overtake 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cli.cjs +3 -2
- package/build/cli.cjs.map +1 -1
- package/build/cli.js +3 -2
- package/build/cli.js.map +1 -1
- package/build/executor.cjs +7 -3
- package/build/executor.cjs.map +1 -1
- package/build/executor.d.ts +1 -2
- package/build/executor.js +7 -3
- package/build/executor.js.map +1 -1
- package/build/index.cjs +6 -9
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.js +6 -9
- package/build/index.js.map +1 -1
- package/build/runner.cjs +7 -4
- package/build/runner.cjs.map +1 -1
- package/build/runner.js +7 -4
- package/build/runner.js.map +1 -1
- package/build/types.cjs.map +1 -1
- package/build/types.d.ts +1 -3
- package/build/types.js.map +1 -1
- package/build/worker.cjs +66 -25
- package/build/worker.cjs.map +1 -1
- package/build/worker.js +67 -26
- package/build/worker.js.map +1 -1
- package/package.json +6 -6
- package/src/cli.ts +5 -4
- package/src/executor.ts +9 -14
- package/src/index.ts +15 -20
- package/src/runner.ts +8 -4
- package/src/types.ts +1 -3
- package/src/worker.ts +77 -28
- package/build/queue.cjs +0 -48
- package/build/queue.cjs.map +0 -1
- package/build/queue.d.ts +0 -3
- package/build/queue.js +0 -38
- package/build/queue.js.map +0 -1
- package/src/queue.ts +0 -42
package/build/worker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { SourceTextModule, SyntheticModule, createContext } from 'node:vm';\nimport { createRequire } from 'node:module';\nimport { fileURLToPath } from 'node:url';\nimport { benchmark } from './runner.js';\nimport { WorkerOptions } from './types.js';\n\nconst {\n
|
|
1
|
+
{"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { SourceTextModule, SyntheticModule, createContext } from 'node:vm';\nimport { createRequire } from 'node:module';\nimport { isAbsolute } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { benchmark } from './runner.js';\nimport { WorkerOptions } from './types.js';\n\nconst {\n benchmarkUrl,\n setupCode,\n teardownCode,\n preCode,\n runCode,\n postCode,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n gcObserver = true,\n\n durationsSAB,\n controlSAB,\n}: WorkerOptions = workerData;\n\nconst serialize = (code?: string) => (code ? code : '() => {}');\n\nconst resolvedBenchmarkUrl = typeof benchmarkUrl === 'string' ? benchmarkUrl : pathToFileURL(process.cwd()).href;\nconst benchmarkDirUrl = new URL('.', resolvedBenchmarkUrl).href;\nconst requireFrom = createRequire(fileURLToPath(new URL('benchmark.js', benchmarkDirUrl)));\n\nconst resolveSpecifier = (specifier: string) => {\n if (specifier.startsWith('file:')) {\n return specifier;\n }\n if (specifier.startsWith('./') || specifier.startsWith('../')) {\n return new URL(specifier, benchmarkDirUrl).href;\n }\n if (isAbsolute(specifier)) {\n return pathToFileURL(specifier).href;\n }\n return requireFrom.resolve(specifier);\n};\n\nconst source = `\nexport const setup = ${serialize(setupCode)};\nexport const teardown = ${serialize(teardownCode)};\nexport const pre = ${serialize(preCode)};\nexport const run = ${serialize(runCode)};\nexport const post = ${serialize(postCode)};\n `;\n\nconst context = createContext({ console, Buffer });\nconst imports = new Map<string, SyntheticModule>();\n\nconst createSyntheticModule = (moduleExports: unknown, exportNames: string[], identifier: string) => {\n const mod = new SyntheticModule(\n exportNames,\n () => {\n for (const name of exportNames) {\n if (name === 'default') {\n mod.setExport(name, moduleExports);\n continue;\n }\n mod.setExport(name, (moduleExports as Record<string, unknown>)[name]);\n }\n },\n { identifier, context },\n );\n return mod;\n};\n\nconst isCjsModule = (target: string) => target.endsWith('.cjs') || target.endsWith('.cts');\n\nconst toRequireTarget = (target: string) => (target.startsWith('file:') ? fileURLToPath(target) : target);\n\nconst loadModule = async (target: string) => {\n const cached = imports.get(target);\n if (cached) return cached;\n\n if (isCjsModule(target)) {\n const required = requireFrom(toRequireTarget(target));\n const exportNames = required && (typeof required === 'object' || typeof required === 'function') ? Object.keys(required) : [];\n if (!exportNames.includes('default')) {\n exportNames.push('default');\n }\n const mod = createSyntheticModule(required, exportNames, target);\n imports.set(target, mod);\n return mod;\n }\n\n const importedModule = await import(target);\n const exportNames = Object.keys(importedModule);\n const mod = createSyntheticModule(importedModule, exportNames, target);\n imports.set(target, mod);\n return mod;\n};\n\nconst loadDynamicModule = async (target: string) => {\n const mod = await loadModule(target);\n if (mod.status !== 'evaluated') {\n await mod.evaluate();\n }\n return mod;\n};\nconst mod = new SourceTextModule(source, {\n identifier: resolvedBenchmarkUrl,\n context,\n initializeImportMeta(meta) {\n meta.url = resolvedBenchmarkUrl;\n },\n importModuleDynamically(specifier) {\n const resolved = resolveSpecifier(specifier);\n return loadDynamicModule(resolved);\n },\n});\n\nawait mod.link(async (specifier) => loadModule(resolveSpecifier(specifier)));\n\nawait mod.evaluate();\nconst { setup, teardown, pre, run, post } = mod.namespace as any;\n\nif (!run) {\n throw new Error('Benchmark run function is required');\n}\n\nprocess.exitCode = await benchmark({\n setup,\n teardown,\n pre,\n run,\n post,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n gcObserver,\n\n durationsSAB,\n controlSAB,\n});\n"],"names":["workerData","SourceTextModule","SyntheticModule","createContext","createRequire","isAbsolute","fileURLToPath","pathToFileURL","benchmark","benchmarkUrl","setupCode","teardownCode","preCode","runCode","postCode","data","warmupCycles","minCycles","absThreshold","relThreshold","gcObserver","durationsSAB","controlSAB","serialize","code","resolvedBenchmarkUrl","process","cwd","href","benchmarkDirUrl","URL","requireFrom","resolveSpecifier","specifier","startsWith","resolve","source","context","console","Buffer","imports","Map","createSyntheticModule","moduleExports","exportNames","identifier","mod","name","setExport","isCjsModule","target","endsWith","toRequireTarget","loadModule","cached","get","required","Object","keys","includes","push","set","importedModule","loadDynamicModule","status","evaluate","initializeImportMeta","meta","url","importModuleDynamically","resolved","link","setup","teardown","pre","run","post","namespace","Error","exitCode"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overtake",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "NodeJS performance benchmark",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -45,17 +45,17 @@
|
|
|
45
45
|
"@swc/jest": "^0.2.39",
|
|
46
46
|
"@types/async": "^3.2.25",
|
|
47
47
|
"@types/jest": "^30.0.0",
|
|
48
|
-
"@types/node": "^
|
|
48
|
+
"@types/node": "^25.0.3",
|
|
49
49
|
"husky": "^9.1.7",
|
|
50
|
-
"inop": "^0.8.
|
|
50
|
+
"inop": "^0.8.10",
|
|
51
51
|
"jest": "^30.2.0",
|
|
52
|
-
"overtake": "^1.
|
|
53
|
-
"prettier": "^3.
|
|
52
|
+
"overtake": "^1.1.1",
|
|
53
|
+
"prettier": "^3.7.4",
|
|
54
54
|
"pretty-quick": "^4.2.2",
|
|
55
55
|
"typescript": "^5.9.3"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@swc/core": "^1.15.
|
|
58
|
+
"@swc/core": "^1.15.8",
|
|
59
59
|
"async": "^3.2.6",
|
|
60
60
|
"commander": "^14.0.2",
|
|
61
61
|
"glob": "^13.0.0"
|
package/src/cli.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { REPORT_TYPES } from './types.js';
|
|
|
10
10
|
|
|
11
11
|
const require = createRequire(import.meta.url);
|
|
12
12
|
const { name, description, version } = require('../package.json');
|
|
13
|
+
const BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');
|
|
13
14
|
|
|
14
15
|
const commander = new Command();
|
|
15
16
|
|
|
@@ -21,8 +22,8 @@ commander
|
|
|
21
22
|
.addOption(new Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(REPORT_TYPES).default(DEFAULT_REPORT_TYPES))
|
|
22
23
|
.addOption(new Option('-w, --workers [workers]', 'number of concurent workers').default(DEFAULT_WORKERS).argParser(parseInt))
|
|
23
24
|
.addOption(new Option('-f, --format [format]', 'output format').default('simple').choices(['simple', 'json', 'pjson', 'table']))
|
|
24
|
-
.addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(
|
|
25
|
-
.addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(
|
|
25
|
+
.addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseFloat))
|
|
26
|
+
.addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseFloat))
|
|
26
27
|
.addOption(new Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt))
|
|
27
28
|
.addOption(new Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt))
|
|
28
29
|
.addOption(new Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt))
|
|
@@ -97,8 +98,8 @@ commander
|
|
|
97
98
|
if (instance) {
|
|
98
99
|
const reports = await instance.execute({
|
|
99
100
|
...executeOptions,
|
|
100
|
-
|
|
101
|
-
});
|
|
101
|
+
[BENCHMARK_URL]: identifier,
|
|
102
|
+
} as typeof executeOptions);
|
|
102
103
|
switch (executeOptions.format) {
|
|
103
104
|
case 'json':
|
|
104
105
|
{
|
package/src/executor.ts
CHANGED
|
@@ -9,23 +9,18 @@ import { RunOptions, ReportOptions, WorkerOptions, BenchmarkOptions, Control, Re
|
|
|
9
9
|
export type ExecutorReport<R extends ReportTypeList> = Record<R[number], Report> & { count: number };
|
|
10
10
|
|
|
11
11
|
export interface ExecutorOptions<R extends ReportTypeList> extends BenchmarkOptions, ReportOptions<R> {
|
|
12
|
-
baseUrl?: string;
|
|
13
12
|
workers?: number;
|
|
14
13
|
maxCycles?: number;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
warmupCycles,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
gcObserver = true,
|
|
26
|
-
reportTypes,
|
|
27
|
-
}: Required<ExecutorOptions<R>>) => {
|
|
28
|
-
const executor = queue<RunOptions<TContext, TInput>>(async ({ baseUrl: runBaseUrl = baseUrl, setup, teardown, pre, run, post, data }) => {
|
|
16
|
+
const BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');
|
|
17
|
+
|
|
18
|
+
export const createExecutor = <TContext, TInput, R extends ReportTypeList>(options: Required<ExecutorOptions<R>>) => {
|
|
19
|
+
const { workers, warmupCycles, maxCycles, minCycles, absThreshold, relThreshold, gcObserver = true, reportTypes } = options;
|
|
20
|
+
const benchmarkUrl = (options as Record<symbol, unknown>)[BENCHMARK_URL];
|
|
21
|
+
const resolvedBenchmarkUrl = typeof benchmarkUrl === 'string' ? benchmarkUrl : pathToFileURL(process.cwd()).href;
|
|
22
|
+
|
|
23
|
+
const executor = queue<RunOptions<TContext, TInput>>(async ({ setup, teardown, pre, run, post, data }) => {
|
|
29
24
|
const setupCode = setup?.toString();
|
|
30
25
|
const teardownCode = teardown?.toString();
|
|
31
26
|
const preCode = pre?.toString();
|
|
@@ -37,7 +32,7 @@ export const createExecutor = <TContext, TInput, R extends ReportTypeList>({
|
|
|
37
32
|
|
|
38
33
|
const workerFile = new URL('./worker.js', import.meta.url);
|
|
39
34
|
const workerData: WorkerOptions = {
|
|
40
|
-
|
|
35
|
+
benchmarkUrl: resolvedBenchmarkUrl,
|
|
41
36
|
setupCode,
|
|
42
37
|
teardownCode,
|
|
43
38
|
preCode,
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { cpus } from 'node:os';
|
|
2
|
-
import { pathToFileURL } from 'node:url';
|
|
3
2
|
import { createExecutor, ExecutorOptions, ExecutorReport } from './executor.js';
|
|
4
3
|
import { MaybePromise, StepFn, SetupFn, TeardownFn, FeedFn, ReportType, ReportTypeList, DEFAULT_CYCLES } from './types.js';
|
|
5
4
|
|
|
@@ -10,6 +9,7 @@ declare global {
|
|
|
10
9
|
export const DEFAULT_WORKERS = cpus().length;
|
|
11
10
|
|
|
12
11
|
export const AsyncFunction = (async () => {}).constructor;
|
|
12
|
+
const BENCHMARK_URL = Symbol.for('overtake.benchmarkUrl');
|
|
13
13
|
|
|
14
14
|
export interface TargetReport<R extends ReportTypeList> {
|
|
15
15
|
target: string;
|
|
@@ -135,29 +135,24 @@ export class Benchmark<TInput> {
|
|
|
135
135
|
return new Target<TContext, TInput>(target);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
async execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>({
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
138
|
+
async execute<R extends readonly ReportType[] = typeof DEFAULT_REPORT_TYPES>(options: ExecutorOptions<R>): Promise<TargetReport<R>[]> {
|
|
139
|
+
const {
|
|
140
|
+
workers = DEFAULT_WORKERS,
|
|
141
|
+
warmupCycles = 20,
|
|
142
|
+
maxCycles = DEFAULT_CYCLES,
|
|
143
|
+
minCycles = 50,
|
|
144
|
+
absThreshold = 1_000,
|
|
145
|
+
relThreshold = 0.02,
|
|
146
|
+
gcObserver = true,
|
|
147
|
+
reportTypes = DEFAULT_REPORT_TYPES as unknown as R,
|
|
148
|
+
} = options;
|
|
149
149
|
if (this.#executed) {
|
|
150
150
|
throw new Error("Benchmark is executed and can't be reused");
|
|
151
151
|
}
|
|
152
152
|
this.#executed = true;
|
|
153
|
-
|
|
154
|
-
const resolvedBaseUrl = baseUrl ?? pathToFileURL(process.cwd()).href;
|
|
155
|
-
if (!baseUrl) {
|
|
156
|
-
console.warn("Overtake: baseUrl not provided; defaulting to process.cwd(). Pass the benchmark's import.meta.url so relative imports resolve correctly.");
|
|
157
|
-
}
|
|
153
|
+
const benchmarkUrl = (options as unknown as Record<symbol, unknown>)[BENCHMARK_URL];
|
|
158
154
|
|
|
159
155
|
const executor = createExecutor<unknown, TInput, R>({
|
|
160
|
-
baseUrl: resolvedBaseUrl,
|
|
161
156
|
workers,
|
|
162
157
|
warmupCycles,
|
|
163
158
|
maxCycles,
|
|
@@ -166,7 +161,8 @@ export class Benchmark<TInput> {
|
|
|
166
161
|
relThreshold,
|
|
167
162
|
gcObserver,
|
|
168
163
|
reportTypes,
|
|
169
|
-
|
|
164
|
+
[BENCHMARK_URL]: benchmarkUrl,
|
|
165
|
+
} as Required<ExecutorOptions<R>>);
|
|
170
166
|
|
|
171
167
|
const reports: TargetReport<R>[] = [];
|
|
172
168
|
for (const target of this.#targets) {
|
|
@@ -177,7 +173,6 @@ export class Benchmark<TInput> {
|
|
|
177
173
|
const data = await feed.fn?.();
|
|
178
174
|
executor
|
|
179
175
|
.push<ExecutorReport<R>>({
|
|
180
|
-
baseUrl: resolvedBaseUrl,
|
|
181
176
|
setup: target.setup,
|
|
182
177
|
teardown: target.teardown,
|
|
183
178
|
pre: measure.pre,
|
package/src/runner.ts
CHANGED
|
@@ -23,6 +23,10 @@ const runAsync = (run: Function) => {
|
|
|
23
23
|
};
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
const isThenable = (value: unknown): value is PromiseLike<unknown> => {
|
|
27
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function') && typeof (value as PromiseLike<unknown>).then === 'function';
|
|
28
|
+
};
|
|
29
|
+
|
|
26
30
|
const TARGET_SAMPLE_NS = 1_000_000n; // aim for ~1ms per measured sample
|
|
27
31
|
const MAX_BATCH = 1_048_576;
|
|
28
32
|
const PROGRESS_STRIDE = 16;
|
|
@@ -198,7 +202,7 @@ export const benchmark = async <TContext, TInput>({
|
|
|
198
202
|
|
|
199
203
|
const context = (await setup?.()) as TContext;
|
|
200
204
|
const maxCycles = durations.length;
|
|
201
|
-
const gcWatcher = new GCWatcher();
|
|
205
|
+
const gcWatcher = gcObserver ? new GCWatcher() : null;
|
|
202
206
|
const gcTracker = gcObserver ? createGCTracker() : null;
|
|
203
207
|
|
|
204
208
|
try {
|
|
@@ -206,7 +210,7 @@ export const benchmark = async <TContext, TInput>({
|
|
|
206
210
|
await pre?.(context, data!);
|
|
207
211
|
const probeStart = hr();
|
|
208
212
|
const probeResult = runRaw(context, data!);
|
|
209
|
-
const isAsync = probeResult
|
|
213
|
+
const isAsync = isThenable(probeResult);
|
|
210
214
|
if (isAsync) {
|
|
211
215
|
await probeResult;
|
|
212
216
|
}
|
|
@@ -282,7 +286,7 @@ export const benchmark = async <TContext, TInput>({
|
|
|
282
286
|
while (true) {
|
|
283
287
|
if (i >= maxCycles) break;
|
|
284
288
|
|
|
285
|
-
const gcMarker = gcWatcher
|
|
289
|
+
const gcMarker = gcWatcher?.start();
|
|
286
290
|
const sampleStart = performance.now();
|
|
287
291
|
let sampleDuration = 0n;
|
|
288
292
|
for (let b = 0; b < batchSize; b++) {
|
|
@@ -298,7 +302,7 @@ export const benchmark = async <TContext, TInput>({
|
|
|
298
302
|
sampleDuration /= BigInt(batchSize);
|
|
299
303
|
|
|
300
304
|
const sampleEnd = performance.now();
|
|
301
|
-
const gcNoise = gcWatcher
|
|
305
|
+
const gcNoise = (gcMarker ? gcWatcher!.seen(gcMarker) : false) || (gcTracker?.overlaps(sampleStart, sampleEnd) ?? false);
|
|
302
306
|
if (gcNoise) {
|
|
303
307
|
continue;
|
|
304
308
|
}
|
package/src/types.ts
CHANGED
|
@@ -34,11 +34,9 @@ export interface BenchmarkOptions {
|
|
|
34
34
|
absThreshold?: number; // ns
|
|
35
35
|
relThreshold?: number; // %
|
|
36
36
|
gcObserver?: boolean;
|
|
37
|
-
baseUrl?: string;
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
export interface RunOptions<TContext, TInput> {
|
|
41
|
-
baseUrl?: string;
|
|
42
40
|
setup?: SetupFn<TContext>;
|
|
43
41
|
teardown?: TeardownFn<TContext>;
|
|
44
42
|
pre?: StepFn<TContext, TInput>;
|
|
@@ -48,7 +46,7 @@ export interface RunOptions<TContext, TInput> {
|
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
export interface WorkerOptions extends Required<BenchmarkOptions> {
|
|
51
|
-
|
|
49
|
+
benchmarkUrl?: string;
|
|
52
50
|
setupCode?: string;
|
|
53
51
|
teardownCode?: string;
|
|
54
52
|
preCode?: string;
|
package/src/worker.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { workerData } from 'node:worker_threads';
|
|
2
2
|
import { SourceTextModule, SyntheticModule, createContext } from 'node:vm';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
-
import {
|
|
4
|
+
import { isAbsolute } from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
6
|
import { benchmark } from './runner.js';
|
|
6
7
|
import { WorkerOptions } from './types.js';
|
|
7
8
|
|
|
8
9
|
const {
|
|
9
|
-
|
|
10
|
+
benchmarkUrl,
|
|
10
11
|
setupCode,
|
|
11
12
|
teardownCode,
|
|
12
13
|
preCode,
|
|
@@ -26,6 +27,23 @@ const {
|
|
|
26
27
|
|
|
27
28
|
const serialize = (code?: string) => (code ? code : '() => {}');
|
|
28
29
|
|
|
30
|
+
const resolvedBenchmarkUrl = typeof benchmarkUrl === 'string' ? benchmarkUrl : pathToFileURL(process.cwd()).href;
|
|
31
|
+
const benchmarkDirUrl = new URL('.', resolvedBenchmarkUrl).href;
|
|
32
|
+
const requireFrom = createRequire(fileURLToPath(new URL('benchmark.js', benchmarkDirUrl)));
|
|
33
|
+
|
|
34
|
+
const resolveSpecifier = (specifier: string) => {
|
|
35
|
+
if (specifier.startsWith('file:')) {
|
|
36
|
+
return specifier;
|
|
37
|
+
}
|
|
38
|
+
if (specifier.startsWith('./') || specifier.startsWith('../')) {
|
|
39
|
+
return new URL(specifier, benchmarkDirUrl).href;
|
|
40
|
+
}
|
|
41
|
+
if (isAbsolute(specifier)) {
|
|
42
|
+
return pathToFileURL(specifier).href;
|
|
43
|
+
}
|
|
44
|
+
return requireFrom.resolve(specifier);
|
|
45
|
+
};
|
|
46
|
+
|
|
29
47
|
const source = `
|
|
30
48
|
export const setup = ${serialize(setupCode)};
|
|
31
49
|
export const teardown = ${serialize(teardownCode)};
|
|
@@ -36,39 +54,71 @@ export const post = ${serialize(postCode)};
|
|
|
36
54
|
|
|
37
55
|
const context = createContext({ console, Buffer });
|
|
38
56
|
const imports = new Map<string, SyntheticModule>();
|
|
39
|
-
const mod = new SourceTextModule(source, {
|
|
40
|
-
identifier: baseUrl,
|
|
41
|
-
context,
|
|
42
|
-
initializeImportMeta(meta) {
|
|
43
|
-
meta.url = baseUrl;
|
|
44
|
-
},
|
|
45
|
-
importModuleDynamically(specifier, referencingModule) {
|
|
46
|
-
const base = referencingModule.identifier ?? baseUrl;
|
|
47
|
-
const resolveFrom = createRequire(fileURLToPath(base));
|
|
48
|
-
return import(resolveFrom.resolve(specifier));
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
const createSyntheticModule = (moduleExports: unknown, exportNames: string[], identifier: string) => {
|
|
59
|
+
const mod = new SyntheticModule(
|
|
60
|
+
exportNames,
|
|
61
|
+
() => {
|
|
62
|
+
for (const name of exportNames) {
|
|
63
|
+
if (name === 'default') {
|
|
64
|
+
mod.setExport(name, moduleExports);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
mod.setExport(name, (moduleExports as Record<string, unknown>)[name]);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{ identifier, context },
|
|
71
|
+
);
|
|
72
|
+
return mod;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const isCjsModule = (target: string) => target.endsWith('.cjs') || target.endsWith('.cts');
|
|
76
|
+
|
|
77
|
+
const toRequireTarget = (target: string) => (target.startsWith('file:') ? fileURLToPath(target) : target);
|
|
78
|
+
|
|
79
|
+
const loadModule = async (target: string) => {
|
|
56
80
|
const cached = imports.get(target);
|
|
57
81
|
if (cached) return cached;
|
|
58
82
|
|
|
83
|
+
if (isCjsModule(target)) {
|
|
84
|
+
const required = requireFrom(toRequireTarget(target));
|
|
85
|
+
const exportNames = required && (typeof required === 'object' || typeof required === 'function') ? Object.keys(required) : [];
|
|
86
|
+
if (!exportNames.includes('default')) {
|
|
87
|
+
exportNames.push('default');
|
|
88
|
+
}
|
|
89
|
+
const mod = createSyntheticModule(required, exportNames, target);
|
|
90
|
+
imports.set(target, mod);
|
|
91
|
+
return mod;
|
|
92
|
+
}
|
|
93
|
+
|
|
59
94
|
const importedModule = await import(target);
|
|
60
95
|
const exportNames = Object.keys(importedModule);
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
|
|
96
|
+
const mod = createSyntheticModule(importedModule, exportNames, target);
|
|
97
|
+
imports.set(target, mod);
|
|
98
|
+
return mod;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const loadDynamicModule = async (target: string) => {
|
|
102
|
+
const mod = await loadModule(target);
|
|
103
|
+
if (mod.status !== 'evaluated') {
|
|
104
|
+
await mod.evaluate();
|
|
105
|
+
}
|
|
106
|
+
return mod;
|
|
107
|
+
};
|
|
108
|
+
const mod = new SourceTextModule(source, {
|
|
109
|
+
identifier: resolvedBenchmarkUrl,
|
|
110
|
+
context,
|
|
111
|
+
initializeImportMeta(meta) {
|
|
112
|
+
meta.url = resolvedBenchmarkUrl;
|
|
113
|
+
},
|
|
114
|
+
importModuleDynamically(specifier) {
|
|
115
|
+
const resolved = resolveSpecifier(specifier);
|
|
116
|
+
return loadDynamicModule(resolved);
|
|
117
|
+
},
|
|
70
118
|
});
|
|
71
119
|
|
|
120
|
+
await mod.link(async (specifier) => loadModule(resolveSpecifier(specifier)));
|
|
121
|
+
|
|
72
122
|
await mod.evaluate();
|
|
73
123
|
const { setup, teardown, pre, run, post } = mod.namespace as any;
|
|
74
124
|
|
|
@@ -77,7 +127,6 @@ if (!run) {
|
|
|
77
127
|
}
|
|
78
128
|
|
|
79
129
|
process.exitCode = await benchmark({
|
|
80
|
-
baseUrl,
|
|
81
130
|
setup,
|
|
82
131
|
teardown,
|
|
83
132
|
pre,
|
package/build/queue.cjs
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
Object.defineProperty(exports, "createQueue", {
|
|
6
|
-
enumerable: true,
|
|
7
|
-
get: function() {
|
|
8
|
-
return createQueue;
|
|
9
|
-
}
|
|
10
|
-
});
|
|
11
|
-
const createQueue = (worker, concurency = 1)=>{
|
|
12
|
-
const queue = new Set();
|
|
13
|
-
const processing = new Map();
|
|
14
|
-
const iterator = queue[Symbol.iterator]();
|
|
15
|
-
let next;
|
|
16
|
-
let counter = 0;
|
|
17
|
-
queueMicrotask(async ()=>{
|
|
18
|
-
while(true){
|
|
19
|
-
if (concurency > 0 && processing.size === concurency) {
|
|
20
|
-
await Promise.race(processing.values());
|
|
21
|
-
}
|
|
22
|
-
if (queue.size === 0) {
|
|
23
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
24
|
-
next = resolve;
|
|
25
|
-
await promise;
|
|
26
|
-
}
|
|
27
|
-
const result = iterator.next();
|
|
28
|
-
if (result.done) {
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
const id = counter++;
|
|
32
|
-
const task = Promise.resolve(worker(result.value)).catch(()=>{}).finally(()=>{
|
|
33
|
-
processing.delete(id);
|
|
34
|
-
});
|
|
35
|
-
processing.set(id, task);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
return {
|
|
39
|
-
push: async (input)=>{
|
|
40
|
-
queue.add(input);
|
|
41
|
-
if (queue.size === 0) {
|
|
42
|
-
next?.();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
//# sourceMappingURL=queue.cjs.map
|
package/build/queue.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queue.ts"],"sourcesContent":["export const createQueue = <T>(worker: (task: T) => Promise<void>, concurency: number = 1) => {\n const queue = new Set<T>();\n const processing = new Map<number, Promise<void>>();\n const iterator = queue[Symbol.iterator]();\n\n let next: () => void;\n let counter = 0;\n\n queueMicrotask(async () => {\n while (true) {\n if (concurency > 0 && processing.size === concurency) {\n await Promise.race(processing.values());\n }\n if (queue.size === 0) {\n const { promise, resolve } = Promise.withResolvers<void>();\n next = resolve;\n await promise;\n }\n const result = iterator.next();\n if (result.done) {\n break;\n }\n const id = counter++;\n const task = Promise.resolve(worker(result.value))\n .catch(() => {})\n .finally(() => {\n processing.delete(id);\n });\n processing.set(id, task);\n }\n });\n\n return {\n push: async (input: T) => {\n queue.add(input);\n\n if (queue.size === 0) {\n next?.();\n }\n },\n };\n};\n"],"names":["createQueue","worker","concurency","queue","Set","processing","Map","iterator","Symbol","next","counter","queueMicrotask","size","Promise","race","values","promise","resolve","withResolvers","result","done","id","task","value","catch","finally","delete","set","push","input","add"],"mappings":";;;;+BAAaA;;;eAAAA;;;AAAN,MAAMA,cAAc,CAAIC,QAAoCC,aAAqB,CAAC;IACvF,MAAMC,QAAQ,IAAIC;IAClB,MAAMC,aAAa,IAAIC;IACvB,MAAMC,WAAWJ,KAAK,CAACK,OAAOD,QAAQ,CAAC;IAEvC,IAAIE;IACJ,IAAIC,UAAU;IAEdC,eAAe;QACb,MAAO,KAAM;YACX,IAAIT,aAAa,KAAKG,WAAWO,IAAI,KAAKV,YAAY;gBACpD,MAAMW,QAAQC,IAAI,CAACT,WAAWU,MAAM;YACtC;YACA,IAAIZ,MAAMS,IAAI,KAAK,GAAG;gBACpB,MAAM,EAAEI,OAAO,EAAEC,OAAO,EAAE,GAAGJ,QAAQK,aAAa;gBAClDT,OAAOQ;gBACP,MAAMD;YACR;YACA,MAAMG,SAASZ,SAASE,IAAI;YAC5B,IAAIU,OAAOC,IAAI,EAAE;gBACf;YACF;YACA,MAAMC,KAAKX;YACX,MAAMY,OAAOT,QAAQI,OAAO,CAAChB,OAAOkB,OAAOI,KAAK,GAC7CC,KAAK,CAAC,KAAO,GACbC,OAAO,CAAC;gBACPpB,WAAWqB,MAAM,CAACL;YACpB;YACFhB,WAAWsB,GAAG,CAACN,IAAIC;QACrB;IACF;IAEA,OAAO;QACLM,MAAM,OAAOC;YACX1B,MAAM2B,GAAG,CAACD;YAEV,IAAI1B,MAAMS,IAAI,KAAK,GAAG;gBACpBH;YACF;QACF;IACF;AACF"}
|
package/build/queue.d.ts
DELETED
package/build/queue.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export const createQueue = (worker, concurency = 1)=>{
|
|
2
|
-
const queue = new Set();
|
|
3
|
-
const processing = new Map();
|
|
4
|
-
const iterator = queue[Symbol.iterator]();
|
|
5
|
-
let next;
|
|
6
|
-
let counter = 0;
|
|
7
|
-
queueMicrotask(async ()=>{
|
|
8
|
-
while(true){
|
|
9
|
-
if (concurency > 0 && processing.size === concurency) {
|
|
10
|
-
await Promise.race(processing.values());
|
|
11
|
-
}
|
|
12
|
-
if (queue.size === 0) {
|
|
13
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
14
|
-
next = resolve;
|
|
15
|
-
await promise;
|
|
16
|
-
}
|
|
17
|
-
const result = iterator.next();
|
|
18
|
-
if (result.done) {
|
|
19
|
-
break;
|
|
20
|
-
}
|
|
21
|
-
const id = counter++;
|
|
22
|
-
const task = Promise.resolve(worker(result.value)).catch(()=>{}).finally(()=>{
|
|
23
|
-
processing.delete(id);
|
|
24
|
-
});
|
|
25
|
-
processing.set(id, task);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
return {
|
|
29
|
-
push: async (input)=>{
|
|
30
|
-
queue.add(input);
|
|
31
|
-
if (queue.size === 0) {
|
|
32
|
-
next?.();
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
//# sourceMappingURL=queue.js.map
|
package/build/queue.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queue.ts"],"sourcesContent":["export const createQueue = <T>(worker: (task: T) => Promise<void>, concurency: number = 1) => {\n const queue = new Set<T>();\n const processing = new Map<number, Promise<void>>();\n const iterator = queue[Symbol.iterator]();\n\n let next: () => void;\n let counter = 0;\n\n queueMicrotask(async () => {\n while (true) {\n if (concurency > 0 && processing.size === concurency) {\n await Promise.race(processing.values());\n }\n if (queue.size === 0) {\n const { promise, resolve } = Promise.withResolvers<void>();\n next = resolve;\n await promise;\n }\n const result = iterator.next();\n if (result.done) {\n break;\n }\n const id = counter++;\n const task = Promise.resolve(worker(result.value))\n .catch(() => {})\n .finally(() => {\n processing.delete(id);\n });\n processing.set(id, task);\n }\n });\n\n return {\n push: async (input: T) => {\n queue.add(input);\n\n if (queue.size === 0) {\n next?.();\n }\n },\n };\n};\n"],"names":["createQueue","worker","concurency","queue","Set","processing","Map","iterator","Symbol","next","counter","queueMicrotask","size","Promise","race","values","promise","resolve","withResolvers","result","done","id","task","value","catch","finally","delete","set","push","input","add"],"mappings":"AAAA,OAAO,MAAMA,cAAc,CAAIC,QAAoCC,aAAqB,CAAC;IACvF,MAAMC,QAAQ,IAAIC;IAClB,MAAMC,aAAa,IAAIC;IACvB,MAAMC,WAAWJ,KAAK,CAACK,OAAOD,QAAQ,CAAC;IAEvC,IAAIE;IACJ,IAAIC,UAAU;IAEdC,eAAe;QACb,MAAO,KAAM;YACX,IAAIT,aAAa,KAAKG,WAAWO,IAAI,KAAKV,YAAY;gBACpD,MAAMW,QAAQC,IAAI,CAACT,WAAWU,MAAM;YACtC;YACA,IAAIZ,MAAMS,IAAI,KAAK,GAAG;gBACpB,MAAM,EAAEI,OAAO,EAAEC,OAAO,EAAE,GAAGJ,QAAQK,aAAa;gBAClDT,OAAOQ;gBACP,MAAMD;YACR;YACA,MAAMG,SAASZ,SAASE,IAAI;YAC5B,IAAIU,OAAOC,IAAI,EAAE;gBACf;YACF;YACA,MAAMC,KAAKX;YACX,MAAMY,OAAOT,QAAQI,OAAO,CAAChB,OAAOkB,OAAOI,KAAK,GAC7CC,KAAK,CAAC,KAAO,GACbC,OAAO,CAAC;gBACPpB,WAAWqB,MAAM,CAACL;YACpB;YACFhB,WAAWsB,GAAG,CAACN,IAAIC;QACrB;IACF;IAEA,OAAO;QACLM,MAAM,OAAOC;YACX1B,MAAM2B,GAAG,CAACD;YAEV,IAAI1B,MAAMS,IAAI,KAAK,GAAG;gBACpBH;YACF;QACF;IACF;AACF,EAAE"}
|
package/src/queue.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export const createQueue = <T>(worker: (task: T) => Promise<void>, concurency: number = 1) => {
|
|
2
|
-
const queue = new Set<T>();
|
|
3
|
-
const processing = new Map<number, Promise<void>>();
|
|
4
|
-
const iterator = queue[Symbol.iterator]();
|
|
5
|
-
|
|
6
|
-
let next: () => void;
|
|
7
|
-
let counter = 0;
|
|
8
|
-
|
|
9
|
-
queueMicrotask(async () => {
|
|
10
|
-
while (true) {
|
|
11
|
-
if (concurency > 0 && processing.size === concurency) {
|
|
12
|
-
await Promise.race(processing.values());
|
|
13
|
-
}
|
|
14
|
-
if (queue.size === 0) {
|
|
15
|
-
const { promise, resolve } = Promise.withResolvers<void>();
|
|
16
|
-
next = resolve;
|
|
17
|
-
await promise;
|
|
18
|
-
}
|
|
19
|
-
const result = iterator.next();
|
|
20
|
-
if (result.done) {
|
|
21
|
-
break;
|
|
22
|
-
}
|
|
23
|
-
const id = counter++;
|
|
24
|
-
const task = Promise.resolve(worker(result.value))
|
|
25
|
-
.catch(() => {})
|
|
26
|
-
.finally(() => {
|
|
27
|
-
processing.delete(id);
|
|
28
|
-
});
|
|
29
|
-
processing.set(id, task);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
push: async (input: T) => {
|
|
35
|
-
queue.add(input);
|
|
36
|
-
|
|
37
|
-
if (queue.size === 0) {
|
|
38
|
-
next?.();
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
};
|