overtake 1.0.3 → 1.0.5
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/README.md +69 -22
- package/bin/overtake.js +1 -1
- package/build/cli.cjs +2 -1
- package/build/cli.cjs.map +1 -1
- package/build/cli.js +2 -1
- package/build/cli.js.map +1 -1
- package/build/runner.cjs +6 -0
- package/build/runner.cjs.map +1 -1
- package/build/runner.js +6 -0
- package/build/runner.js.map +1 -1
- package/build/worker.cjs +6 -0
- package/build/worker.cjs.map +1 -1
- package/build/worker.d.ts +1 -1
- package/build/worker.js +1 -1
- package/build/worker.js.map +1 -1
- package/examples/complete.ts +3 -3
- package/examples/custom-reports.ts +22 -0
- package/examples/imports.ts +51 -0
- package/examples/quick-start.ts +8 -9
- package/package.json +4 -4
- package/src/cli.ts +4 -1
- package/src/runner.ts +7 -0
- package/src/worker.ts +1 -1
- package/CLAUDE.md +0 -145
- package/examples/array-copy.ts +0 -17
- package/examples/object-merge.ts +0 -41
- package/examples/serialization.ts +0 -22
package/README.md
CHANGED
|
@@ -19,10 +19,11 @@ const suite = benchmark('1M numbers', () => Array.from({ length: 1e6 }, (_, i) =
|
|
|
19
19
|
suite.target('for loop').measure('sum', (_, arr) => {
|
|
20
20
|
let sum = 0;
|
|
21
21
|
for (let i = 0; i < arr.length; i++) sum += arr[i];
|
|
22
|
-
return sum;
|
|
23
22
|
});
|
|
24
23
|
|
|
25
|
-
suite.target('reduce').measure('sum', (_, arr) =>
|
|
24
|
+
suite.target('reduce').measure('sum', (_, arr) => {
|
|
25
|
+
arr.reduce((a, b) => a + b);
|
|
26
|
+
});
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
```bash
|
|
@@ -85,9 +86,36 @@ benchmark('data', getData)
|
|
|
85
86
|
const { serialize } = await import('node:v8');
|
|
86
87
|
return { serialize };
|
|
87
88
|
})
|
|
88
|
-
.measure('serialize', ({ serialize }, input) =>
|
|
89
|
+
.measure('serialize', ({ serialize }, input) => {
|
|
90
|
+
serialize(input);
|
|
91
|
+
});
|
|
89
92
|
```
|
|
90
93
|
|
|
94
|
+
### Importing Local Files
|
|
95
|
+
|
|
96
|
+
**Important**: Dynamic imports inside target functions resolve relative to the worker location (`node_modules/overtake/build/worker.js`), not your project root. For local files, you must construct absolute paths:
|
|
97
|
+
|
|
98
|
+
````typescript
|
|
99
|
+
// ❌ WRONG - Relative path resolves from worker location
|
|
100
|
+
.target('myImpl', async () => {
|
|
101
|
+
const { myFunc } = await import('./utils/myModule.js'); // Error: looks in node_modules/overtake/build/utils/
|
|
102
|
+
return { myFunc };
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// ✅ CORRECT - Build absolute path inside the function
|
|
106
|
+
.target('myImpl', async () => {
|
|
107
|
+
const { join } = await import('node:path');
|
|
108
|
+
const modulePath = join(process.cwd(), './utils/myModule.js');
|
|
109
|
+
const { myFunc } = await import(modulePath);
|
|
110
|
+
return { myFunc };
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// ✅ ALSO CORRECT - For node_modules packages
|
|
114
|
+
.target('lib', async () => {
|
|
115
|
+
const { someFunc } = await import('my-package');
|
|
116
|
+
return { someFunc };
|
|
117
|
+
})
|
|
118
|
+
|
|
91
119
|
## Usage
|
|
92
120
|
|
|
93
121
|
### CLI Mode (Recommended)
|
|
@@ -99,12 +127,14 @@ When using `npx overtake`, a global `benchmark` function is provided:
|
|
|
99
127
|
benchmark('small', () => generateSmallData())
|
|
100
128
|
.feed('large', () => generateLargeData())
|
|
101
129
|
.target('algorithm A')
|
|
102
|
-
.measure('process', (_, input) =>
|
|
130
|
+
.measure('process', (_, input) => {
|
|
131
|
+
processA(input);
|
|
132
|
+
})
|
|
103
133
|
.target('algorithm B')
|
|
104
|
-
.measure('process', (_, input) =>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
134
|
+
.measure('process', (_, input) => {
|
|
135
|
+
processB(input);
|
|
136
|
+
});
|
|
137
|
+
````
|
|
108
138
|
|
|
109
139
|
```bash
|
|
110
140
|
npx overtake benchmark.ts --format table
|
|
@@ -119,7 +149,9 @@ import { Benchmark, printTableReports } from 'overtake';
|
|
|
119
149
|
|
|
120
150
|
const suite = new Benchmark('dataset', () => getData());
|
|
121
151
|
|
|
122
|
-
suite.target('impl').measure('op', (_, input) =>
|
|
152
|
+
suite.target('impl').measure('op', (_, input) => {
|
|
153
|
+
process(input);
|
|
154
|
+
});
|
|
123
155
|
|
|
124
156
|
// Must explicitly execute
|
|
125
157
|
const reports = await suite.execute({
|
|
@@ -167,7 +199,6 @@ suite
|
|
|
167
199
|
// ctx contains setup return value
|
|
168
200
|
const hash = createHash('sha256').update(input).digest();
|
|
169
201
|
cache.set(input, hash);
|
|
170
|
-
return hash;
|
|
171
202
|
});
|
|
172
203
|
```
|
|
173
204
|
|
|
@@ -184,29 +215,45 @@ suite
|
|
|
184
215
|
.measure('process', ({ gcBlock }, input) => {
|
|
185
216
|
const result = input.map((x) => x * x);
|
|
186
217
|
gcBlock.add(result); // Prevent GC during measurement
|
|
187
|
-
return result;
|
|
188
218
|
});
|
|
189
219
|
```
|
|
190
220
|
|
|
191
221
|
## Examples
|
|
192
222
|
|
|
193
|
-
###
|
|
223
|
+
### Compare Algorithms
|
|
194
224
|
|
|
195
225
|
```typescript
|
|
196
|
-
//
|
|
197
|
-
const
|
|
226
|
+
// examples/quick-start.ts
|
|
227
|
+
const sumBenchmark = benchmark('1M numbers', () => Array.from({ length: 1_000_000 }, (_, i) => i));
|
|
198
228
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
})
|
|
204
|
-
.measure('serialize', ({ serialize }, input) => serialize(input));
|
|
229
|
+
sumBenchmark.target('for loop').measure('sum', (_, numbers) => {
|
|
230
|
+
let sum = 0;
|
|
231
|
+
for (let i = 0; i < numbers.length; i++) sum += numbers[i];
|
|
232
|
+
});
|
|
205
233
|
|
|
206
|
-
|
|
234
|
+
sumBenchmark.target('reduce').measure('sum', (_, numbers) => {
|
|
235
|
+
numbers.reduce((a, b) => a + b, 0);
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Import Local Modules
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// examples/imports.ts - Correct way to import local files
|
|
243
|
+
.target('local files', async () => {
|
|
244
|
+
const { join } = await import('node:path');
|
|
245
|
+
const modulePath = join(process.cwd(), './build/myModule.js');
|
|
246
|
+
const { myFunction } = await import(modulePath);
|
|
247
|
+
return { myFunction };
|
|
248
|
+
})
|
|
207
249
|
```
|
|
208
250
|
|
|
209
|
-
**[📁
|
|
251
|
+
**[📁 See all examples](./examples/):**
|
|
252
|
+
|
|
253
|
+
- `quick-start.ts` - Minimal benchmark example
|
|
254
|
+
- `complete.ts` - All features (setup/teardown, pre/post hooks, multiple feeds)
|
|
255
|
+
- `imports.ts` - Import patterns and memory management
|
|
256
|
+
- `custom-reports.ts` - Statistics and custom report types
|
|
210
257
|
|
|
211
258
|
## CLI Options
|
|
212
259
|
|
package/bin/overtake.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env -S node --experimental-vm-modules --no-warnings
|
|
1
|
+
#!/usr/bin/env -S node --experimental-vm-modules --no-warnings --expose-gc
|
|
2
2
|
import '../build/cli.js';
|
package/build/cli.cjs
CHANGED
|
@@ -96,7 +96,8 @@ commander.name(name).description(description).version(version).argument('<path>'
|
|
|
96
96
|
};
|
|
97
97
|
const script = new _nodevm.SourceTextModule(code, {
|
|
98
98
|
context: (0, _nodevm.createContext)({
|
|
99
|
-
benchmark
|
|
99
|
+
benchmark,
|
|
100
|
+
Buffer
|
|
100
101
|
})
|
|
101
102
|
});
|
|
102
103
|
const imports = new Map();
|
package/build/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { createRequire, Module } from 'node:module';\nimport { SyntheticModule, createContext, SourceTextModule } from 'node:vm';\nimport { stat, readFile } from 'node:fs/promises';\nimport { transform } from '@swc/core';\nimport { Command, Option } from 'commander';\nimport { glob } from 'glob';\nimport { Benchmark, printTableReports, printJSONReports, printSimpleReports, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from './index.js';\nimport { REPORT_TYPES } from './types.js';\n\nconst require = createRequire(import.meta.url);\nconst { name, description, version } = require('../package.json');\n\nconst commander = new Command();\n\nconst transpile = async (code: string): Promise<string> => {\n const output = await transform(code, {\n filename: 'benchmark.ts',\n jsc: {\n parser: {\n syntax: 'typescript',\n tsx: false,\n dynamicImport: true,\n },\n target: 'esnext',\n },\n module: {\n type: 'es6',\n },\n });\n return output.code;\n};\n\ncommander\n .name(name)\n .description(description)\n .version(version)\n .argument('<path>', 'glob pattern to find benchmarks')\n .addOption(new Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(REPORT_TYPES).default(DEFAULT_REPORT_TYPES))\n .addOption(new Option('-w, --workers [workers]', 'number of concurent workers').default(DEFAULT_WORKERS).argParser(parseInt))\n .addOption(new Option('-f, --format [format]', 'output format').default('simple').choices(['simple', 'json', 'pjson', 'table']))\n .addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt))\n .addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt))\n .addOption(new Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt))\n .addOption(new Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt))\n .addOption(new Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt))\n .action(async (path, executeOptions) => {\n const files = await glob(path, { absolute: true, cwd: process.cwd() }).catch(() => []);\n for (const file of files) {\n const stats = await stat(file).catch(() => false as const);\n if (stats && stats.isFile()) {\n const content = await readFile(file, 'utf8');\n const code = await transpile(content);\n let instance: Benchmark<unknown> | undefined;\n const benchmark = (...args: Parameters<(typeof Benchmark)['create']>) => {\n if (instance) {\n throw new Error('Only one benchmark per file is supported');\n }\n instance = Benchmark.create(...args);\n return instance;\n };\n const script = new SourceTextModule(code, {\n context: createContext({
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { createRequire, Module } from 'node:module';\nimport { SyntheticModule, createContext, SourceTextModule } from 'node:vm';\nimport { stat, readFile } from 'node:fs/promises';\nimport { transform } from '@swc/core';\nimport { Command, Option } from 'commander';\nimport { glob } from 'glob';\nimport { Benchmark, printTableReports, printJSONReports, printSimpleReports, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from './index.js';\nimport { REPORT_TYPES } from './types.js';\n\nconst require = createRequire(import.meta.url);\nconst { name, description, version } = require('../package.json');\n\nconst commander = new Command();\n\nconst transpile = async (code: string): Promise<string> => {\n const output = await transform(code, {\n filename: 'benchmark.ts',\n jsc: {\n parser: {\n syntax: 'typescript',\n tsx: false,\n dynamicImport: true,\n },\n target: 'esnext',\n },\n module: {\n type: 'es6',\n },\n });\n return output.code;\n};\n\ncommander\n .name(name)\n .description(description)\n .version(version)\n .argument('<path>', 'glob pattern to find benchmarks')\n .addOption(new Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(REPORT_TYPES).default(DEFAULT_REPORT_TYPES))\n .addOption(new Option('-w, --workers [workers]', 'number of concurent workers').default(DEFAULT_WORKERS).argParser(parseInt))\n .addOption(new Option('-f, --format [format]', 'output format').default('simple').choices(['simple', 'json', 'pjson', 'table']))\n .addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt))\n .addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt))\n .addOption(new Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt))\n .addOption(new Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt))\n .addOption(new Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt))\n .action(async (path, executeOptions) => {\n const files = await glob(path, { absolute: true, cwd: process.cwd() }).catch(() => []);\n for (const file of files) {\n const stats = await stat(file).catch(() => false as const);\n if (stats && stats.isFile()) {\n const content = await readFile(file, 'utf8');\n const code = await transpile(content);\n let instance: Benchmark<unknown> | undefined;\n const benchmark = (...args: Parameters<(typeof Benchmark)['create']>) => {\n if (instance) {\n throw new Error('Only one benchmark per file is supported');\n }\n instance = Benchmark.create(...args);\n return instance;\n };\n const script = new SourceTextModule(code, {\n context: createContext({\n benchmark,\n Buffer,\n }),\n });\n const imports = new Map();\n await script.link(async (specifier: string, referencingModule) => {\n if (imports.has(specifier)) {\n return imports.get(specifier);\n }\n const mod = await import(Module.isBuiltin(specifier) ? specifier : require.resolve(specifier));\n const exportNames = Object.keys(mod);\n const imported = new SyntheticModule(\n exportNames,\n () => {\n exportNames.forEach((key) => imported.setExport(key, mod[key]));\n },\n { identifier: specifier, context: referencingModule.context },\n );\n\n imports.set(specifier, imported);\n return imported;\n });\n await script.evaluate();\n\n if (instance) {\n const reports = await instance.execute(executeOptions);\n switch (executeOptions.format) {\n case 'json':\n {\n printJSONReports(reports);\n }\n break;\n case 'pjson':\n {\n printJSONReports(reports, 2);\n }\n break;\n case 'table':\n {\n printTableReports(reports);\n }\n break;\n default:\n printSimpleReports(reports);\n }\n }\n }\n }\n });\n\ncommander.parse(process.argv);\n"],"names":["require","createRequire","name","description","version","commander","Command","transpile","code","output","transform","filename","jsc","parser","syntax","tsx","dynamicImport","target","module","type","argument","addOption","Option","choices","REPORT_TYPES","default","DEFAULT_REPORT_TYPES","DEFAULT_WORKERS","argParser","parseInt","action","path","executeOptions","files","glob","absolute","cwd","process","catch","file","stats","stat","isFile","content","readFile","instance","benchmark","args","Error","Benchmark","create","script","SourceTextModule","context","createContext","Buffer","imports","Map","link","specifier","referencingModule","has","get","mod","Module","isBuiltin","resolve","exportNames","Object","keys","imported","SyntheticModule","forEach","key","setExport","identifier","set","evaluate","reports","execute","format","printJSONReports","printTableReports","printSimpleReports","parse","argv"],"mappings":"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"}
|
package/build/cli.js
CHANGED
package/build/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { createRequire, Module } from 'node:module';\nimport { SyntheticModule, createContext, SourceTextModule } from 'node:vm';\nimport { stat, readFile } from 'node:fs/promises';\nimport { transform } from '@swc/core';\nimport { Command, Option } from 'commander';\nimport { glob } from 'glob';\nimport { Benchmark, printTableReports, printJSONReports, printSimpleReports, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from './index.js';\nimport { REPORT_TYPES } from './types.js';\n\nconst require = createRequire(import.meta.url);\nconst { name, description, version } = require('../package.json');\n\nconst commander = new Command();\n\nconst transpile = async (code: string): Promise<string> => {\n const output = await transform(code, {\n filename: 'benchmark.ts',\n jsc: {\n parser: {\n syntax: 'typescript',\n tsx: false,\n dynamicImport: true,\n },\n target: 'esnext',\n },\n module: {\n type: 'es6',\n },\n });\n return output.code;\n};\n\ncommander\n .name(name)\n .description(description)\n .version(version)\n .argument('<path>', 'glob pattern to find benchmarks')\n .addOption(new Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(REPORT_TYPES).default(DEFAULT_REPORT_TYPES))\n .addOption(new Option('-w, --workers [workers]', 'number of concurent workers').default(DEFAULT_WORKERS).argParser(parseInt))\n .addOption(new Option('-f, --format [format]', 'output format').default('simple').choices(['simple', 'json', 'pjson', 'table']))\n .addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt))\n .addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt))\n .addOption(new Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt))\n .addOption(new Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt))\n .addOption(new Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt))\n .action(async (path, executeOptions) => {\n const files = await glob(path, { absolute: true, cwd: process.cwd() }).catch(() => []);\n for (const file of files) {\n const stats = await stat(file).catch(() => false as const);\n if (stats && stats.isFile()) {\n const content = await readFile(file, 'utf8');\n const code = await transpile(content);\n let instance: Benchmark<unknown> | undefined;\n const benchmark = (...args: Parameters<(typeof Benchmark)['create']>) => {\n if (instance) {\n throw new Error('Only one benchmark per file is supported');\n }\n instance = Benchmark.create(...args);\n return instance;\n };\n const script = new SourceTextModule(code, {\n context: createContext({
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { createRequire, Module } from 'node:module';\nimport { SyntheticModule, createContext, SourceTextModule } from 'node:vm';\nimport { stat, readFile } from 'node:fs/promises';\nimport { transform } from '@swc/core';\nimport { Command, Option } from 'commander';\nimport { glob } from 'glob';\nimport { Benchmark, printTableReports, printJSONReports, printSimpleReports, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from './index.js';\nimport { REPORT_TYPES } from './types.js';\n\nconst require = createRequire(import.meta.url);\nconst { name, description, version } = require('../package.json');\n\nconst commander = new Command();\n\nconst transpile = async (code: string): Promise<string> => {\n const output = await transform(code, {\n filename: 'benchmark.ts',\n jsc: {\n parser: {\n syntax: 'typescript',\n tsx: false,\n dynamicImport: true,\n },\n target: 'esnext',\n },\n module: {\n type: 'es6',\n },\n });\n return output.code;\n};\n\ncommander\n .name(name)\n .description(description)\n .version(version)\n .argument('<path>', 'glob pattern to find benchmarks')\n .addOption(new Option('-r, --report-types [reportTypes...]', 'statistic types to include in the report').choices(REPORT_TYPES).default(DEFAULT_REPORT_TYPES))\n .addOption(new Option('-w, --workers [workers]', 'number of concurent workers').default(DEFAULT_WORKERS).argParser(parseInt))\n .addOption(new Option('-f, --format [format]', 'output format').default('simple').choices(['simple', 'json', 'pjson', 'table']))\n .addOption(new Option('--abs-threshold [absThreshold]', 'absolute error threshold in nanoseconds').argParser(parseInt))\n .addOption(new Option('--rel-threshold [relThreshold]', 'relative error threshold (fraction between 0 and 1)').argParser(parseInt))\n .addOption(new Option('--warmup-cycles [warmupCycles]', 'number of warmup cycles before measuring').argParser(parseInt))\n .addOption(new Option('--max-cycles [maxCycles]', 'maximum measurement cycles per feed').argParser(parseInt))\n .addOption(new Option('--min-cycles [minCycles]', 'minimum measurement cycles per feed').argParser(parseInt))\n .action(async (path, executeOptions) => {\n const files = await glob(path, { absolute: true, cwd: process.cwd() }).catch(() => []);\n for (const file of files) {\n const stats = await stat(file).catch(() => false as const);\n if (stats && stats.isFile()) {\n const content = await readFile(file, 'utf8');\n const code = await transpile(content);\n let instance: Benchmark<unknown> | undefined;\n const benchmark = (...args: Parameters<(typeof Benchmark)['create']>) => {\n if (instance) {\n throw new Error('Only one benchmark per file is supported');\n }\n instance = Benchmark.create(...args);\n return instance;\n };\n const script = new SourceTextModule(code, {\n context: createContext({\n benchmark,\n Buffer,\n }),\n });\n const imports = new Map();\n await script.link(async (specifier: string, referencingModule) => {\n if (imports.has(specifier)) {\n return imports.get(specifier);\n }\n const mod = await import(Module.isBuiltin(specifier) ? specifier : require.resolve(specifier));\n const exportNames = Object.keys(mod);\n const imported = new SyntheticModule(\n exportNames,\n () => {\n exportNames.forEach((key) => imported.setExport(key, mod[key]));\n },\n { identifier: specifier, context: referencingModule.context },\n );\n\n imports.set(specifier, imported);\n return imported;\n });\n await script.evaluate();\n\n if (instance) {\n const reports = await instance.execute(executeOptions);\n switch (executeOptions.format) {\n case 'json':\n {\n printJSONReports(reports);\n }\n break;\n case 'pjson':\n {\n printJSONReports(reports, 2);\n }\n break;\n case 'table':\n {\n printTableReports(reports);\n }\n break;\n default:\n printSimpleReports(reports);\n }\n }\n }\n }\n });\n\ncommander.parse(process.argv);\n"],"names":["createRequire","Module","SyntheticModule","createContext","SourceTextModule","stat","readFile","transform","Command","Option","glob","Benchmark","printTableReports","printJSONReports","printSimpleReports","DEFAULT_REPORT_TYPES","DEFAULT_WORKERS","REPORT_TYPES","require","url","name","description","version","commander","transpile","code","output","filename","jsc","parser","syntax","tsx","dynamicImport","target","module","type","argument","addOption","choices","default","argParser","parseInt","action","path","executeOptions","files","absolute","cwd","process","catch","file","stats","isFile","content","instance","benchmark","args","Error","create","script","context","Buffer","imports","Map","link","specifier","referencingModule","has","get","mod","isBuiltin","resolve","exportNames","Object","keys","imported","forEach","key","setExport","identifier","set","evaluate","reports","execute","format","parse","argv"],"mappings":"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"}
|
package/build/runner.cjs
CHANGED
|
@@ -36,6 +36,8 @@ const benchmark = async ({ setup, teardown, pre, run: runRaw, post, data, warmup
|
|
|
36
36
|
await pre?.(context, data);
|
|
37
37
|
const result = runRaw(context, data);
|
|
38
38
|
await post?.(context, data);
|
|
39
|
+
global.gc?.();
|
|
40
|
+
global.gc?.();
|
|
39
41
|
const run = result instanceof Promise ? runAsync(runRaw) : runSync(runRaw);
|
|
40
42
|
const start = Date.now();
|
|
41
43
|
while(Date.now() - start < 1_000){
|
|
@@ -45,6 +47,8 @@ const benchmark = async ({ setup, teardown, pre, run: runRaw, post, data, warmup
|
|
|
45
47
|
await pre?.(context, data);
|
|
46
48
|
await run(context, data);
|
|
47
49
|
await post?.(context, data);
|
|
50
|
+
global.gc?.();
|
|
51
|
+
global.gc?.();
|
|
48
52
|
}
|
|
49
53
|
let i = 0;
|
|
50
54
|
let mean = 0n;
|
|
@@ -54,6 +58,8 @@ const benchmark = async ({ setup, teardown, pre, run: runRaw, post, data, warmup
|
|
|
54
58
|
await pre?.(context, data);
|
|
55
59
|
const duration = await run(context, data);
|
|
56
60
|
await post?.(context, data);
|
|
61
|
+
global.gc?.();
|
|
62
|
+
global.gc?.();
|
|
57
63
|
durations[i++] = duration;
|
|
58
64
|
const delta = duration - mean;
|
|
59
65
|
mean += delta / BigInt(i);
|
package/build/runner.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runner.ts"],"sourcesContent":["import { Options, Control } from './types.js';\n\nconst COMPLETE_VALUE = 100_00;\n\nconst runSync = (run: Function) => {\n return (...args: unknown[]) => {\n const start = process.hrtime.bigint();\n run(...args);\n return process.hrtime.bigint() - start;\n };\n};\n\nconst runAsync = (run: Function) => {\n return async (...args: unknown[]) => {\n const start = process.hrtime.bigint();\n await run(...args);\n return process.hrtime.bigint() - start;\n };\n};\n\nexport const benchmark = async <TContext, TInput>({\n setup,\n teardown,\n pre,\n run: runRaw,\n post,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n}: Required<Options<TContext, TInput>>) => {\n const durations = new BigUint64Array(durationsSAB);\n const control = new Int32Array(controlSAB);\n\n control[Control.INDEX] = 0;\n control[Control.PROGRESS] = 0;\n control[Control.COMPLETE] = 255;\n\n const context = (await setup?.()) as TContext;\n const maxCycles = durations.length;\n\n try {\n await pre?.(context, data!);\n const result = runRaw(context, data!);\n await post?.(context, data!);\n const run = result instanceof Promise ? runAsync(runRaw) : runSync(runRaw);\n const start = Date.now();\n while (Date.now() - start < 1_000) {\n Math.sqrt(Math.random());\n }\n for (let i = 0; i < warmupCycles; i++) {\n await pre?.(context, data!);\n await run(context, data);\n await post?.(context, data!);\n }\n\n let i = 0;\n let mean = 0n;\n let m2 = 0n;\n\n while (true) {\n if (i >= maxCycles) break;\n\n await pre?.(context, data!);\n const duration = await run(context, data);\n await post?.(context, data!);\n\n durations[i++] = duration;\n const delta = duration - mean;\n mean += delta / BigInt(i);\n m2 += delta * (duration - mean);\n\n const progress = Math.max(i / maxCycles) * COMPLETE_VALUE;\n control[Control.PROGRESS] = progress;\n\n if (i >= minCycles) {\n const variance = Number(m2) / (i - 1);\n const stddev = Math.sqrt(variance);\n if (stddev <= Number(absThreshold)) {\n break;\n }\n\n const meanNum = Number(mean);\n const cov = stddev / (meanNum || 1);\n if (cov <= relThreshold) {\n break;\n }\n }\n }\n\n control[Control.INDEX] = i;\n control[Control.COMPLETE] = 0;\n } catch (e) {\n console.error(e && typeof e === 'object' && 'stack' in e ? e.stack : e);\n control[Control.COMPLETE] = 1;\n } finally {\n try {\n await teardown?.(context);\n } catch (e) {\n control[Control.COMPLETE] = 2;\n console.error(e && typeof e === 'object' && 'stack' in e ? e.stack : e);\n }\n }\n\n return control[Control.COMPLETE];\n};\n"],"names":["benchmark","COMPLETE_VALUE","runSync","run","args","start","process","hrtime","bigint","runAsync","setup","teardown","pre","runRaw","post","data","warmupCycles","minCycles","absThreshold","relThreshold","durationsSAB","controlSAB","durations","BigUint64Array","control","Int32Array","Control","INDEX","PROGRESS","COMPLETE","context","maxCycles","length","result","Promise","Date","now","Math","sqrt","random","i","mean","m2","duration","delta","BigInt","progress","max","variance","Number","stddev","meanNum","cov","e","console","error","stack"],"mappings":";;;;+BAoBaA;;;eAAAA;;;0BApBoB;AAEjC,MAAMC,iBAAiB;AAEvB,MAAMC,UAAU,CAACC;IACf,OAAO,CAAC,GAAGC;QACT,MAAMC,QAAQC,QAAQC,MAAM,CAACC,MAAM;QACnCL,OAAOC;QACP,OAAOE,QAAQC,MAAM,CAACC,MAAM,KAAKH;IACnC;AACF;AAEA,MAAMI,WAAW,CAACN;IAChB,OAAO,OAAO,GAAGC;QACf,MAAMC,QAAQC,QAAQC,MAAM,CAACC,MAAM;QACnC,MAAML,OAAOC;QACb,OAAOE,QAAQC,MAAM,CAACC,MAAM,KAAKH;IACnC;AACF;AAEO,MAAML,YAAY,OAAyB,EAChDU,KAAK,EACLC,QAAQ,EACRC,GAAG,EACHT,KAAKU,MAAM,EACXC,IAAI,EACJC,IAAI,EAEJC,YAAY,EACZC,SAAS,EACTC,YAAY,EACZC,YAAY,EAEZC,YAAY,EACZC,UAAU,EAC0B;IACpC,MAAMC,YAAY,IAAIC,eAAeH;IACrC,MAAMI,UAAU,IAAIC,WAAWJ;IAE/BG,OAAO,CAACE,iBAAO,CAACC,KAAK,CAAC,GAAG;IACzBH,OAAO,CAACE,iBAAO,CAACE,QAAQ,CAAC,GAAG;IAC5BJ,OAAO,CAACE,iBAAO,CAACG,QAAQ,CAAC,GAAG;IAE5B,MAAMC,UAAW,MAAMpB;IACvB,MAAMqB,YAAYT,UAAUU,MAAM;IAElC,IAAI;QACF,MAAMpB,MAAMkB,SAASf;QACrB,MAAMkB,SAASpB,OAAOiB,SAASf;QAC/B,MAAMD,OAAOgB,SAASf;
|
|
1
|
+
{"version":3,"sources":["../src/runner.ts"],"sourcesContent":["import { Options, Control } from './types.js';\n\nconst COMPLETE_VALUE = 100_00;\n\nconst runSync = (run: Function) => {\n return (...args: unknown[]) => {\n const start = process.hrtime.bigint();\n run(...args);\n return process.hrtime.bigint() - start;\n };\n};\n\nconst runAsync = (run: Function) => {\n return async (...args: unknown[]) => {\n const start = process.hrtime.bigint();\n await run(...args);\n return process.hrtime.bigint() - start;\n };\n};\n\nexport const benchmark = async <TContext, TInput>({\n setup,\n teardown,\n pre,\n run: runRaw,\n post,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n}: Required<Options<TContext, TInput>>) => {\n const durations = new BigUint64Array(durationsSAB);\n const control = new Int32Array(controlSAB);\n\n control[Control.INDEX] = 0;\n control[Control.PROGRESS] = 0;\n control[Control.COMPLETE] = 255;\n\n const context = (await setup?.()) as TContext;\n const maxCycles = durations.length;\n\n try {\n await pre?.(context, data!);\n const result = runRaw(context, data!);\n await post?.(context, data!);\n global.gc?.();\n global.gc?.();\n\n const run = result instanceof Promise ? runAsync(runRaw) : runSync(runRaw);\n const start = Date.now();\n while (Date.now() - start < 1_000) {\n Math.sqrt(Math.random());\n }\n for (let i = 0; i < warmupCycles; i++) {\n await pre?.(context, data!);\n await run(context, data);\n await post?.(context, data!);\n global.gc?.();\n global.gc?.();\n }\n\n let i = 0;\n let mean = 0n;\n let m2 = 0n;\n\n while (true) {\n if (i >= maxCycles) break;\n\n await pre?.(context, data!);\n const duration = await run(context, data);\n await post?.(context, data!);\n global.gc?.();\n global.gc?.();\n\n durations[i++] = duration;\n const delta = duration - mean;\n mean += delta / BigInt(i);\n m2 += delta * (duration - mean);\n\n const progress = Math.max(i / maxCycles) * COMPLETE_VALUE;\n control[Control.PROGRESS] = progress;\n\n if (i >= minCycles) {\n const variance = Number(m2) / (i - 1);\n const stddev = Math.sqrt(variance);\n if (stddev <= Number(absThreshold)) {\n break;\n }\n\n const meanNum = Number(mean);\n const cov = stddev / (meanNum || 1);\n if (cov <= relThreshold) {\n break;\n }\n }\n }\n\n control[Control.INDEX] = i;\n control[Control.COMPLETE] = 0;\n } catch (e) {\n console.error(e && typeof e === 'object' && 'stack' in e ? e.stack : e);\n control[Control.COMPLETE] = 1;\n } finally {\n try {\n await teardown?.(context);\n } catch (e) {\n control[Control.COMPLETE] = 2;\n console.error(e && typeof e === 'object' && 'stack' in e ? e.stack : e);\n }\n }\n\n return control[Control.COMPLETE];\n};\n"],"names":["benchmark","COMPLETE_VALUE","runSync","run","args","start","process","hrtime","bigint","runAsync","setup","teardown","pre","runRaw","post","data","warmupCycles","minCycles","absThreshold","relThreshold","durationsSAB","controlSAB","durations","BigUint64Array","control","Int32Array","Control","INDEX","PROGRESS","COMPLETE","context","maxCycles","length","result","global","gc","Promise","Date","now","Math","sqrt","random","i","mean","m2","duration","delta","BigInt","progress","max","variance","Number","stddev","meanNum","cov","e","console","error","stack"],"mappings":"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"}
|
package/build/runner.js
CHANGED
|
@@ -26,6 +26,8 @@ export const benchmark = async ({ setup, teardown, pre, run: runRaw, post, data,
|
|
|
26
26
|
await pre?.(context, data);
|
|
27
27
|
const result = runRaw(context, data);
|
|
28
28
|
await post?.(context, data);
|
|
29
|
+
global.gc?.();
|
|
30
|
+
global.gc?.();
|
|
29
31
|
const run = result instanceof Promise ? runAsync(runRaw) : runSync(runRaw);
|
|
30
32
|
const start = Date.now();
|
|
31
33
|
while(Date.now() - start < 1_000){
|
|
@@ -35,6 +37,8 @@ export const benchmark = async ({ setup, teardown, pre, run: runRaw, post, data,
|
|
|
35
37
|
await pre?.(context, data);
|
|
36
38
|
await run(context, data);
|
|
37
39
|
await post?.(context, data);
|
|
40
|
+
global.gc?.();
|
|
41
|
+
global.gc?.();
|
|
38
42
|
}
|
|
39
43
|
let i = 0;
|
|
40
44
|
let mean = 0n;
|
|
@@ -44,6 +48,8 @@ export const benchmark = async ({ setup, teardown, pre, run: runRaw, post, data,
|
|
|
44
48
|
await pre?.(context, data);
|
|
45
49
|
const duration = await run(context, data);
|
|
46
50
|
await post?.(context, data);
|
|
51
|
+
global.gc?.();
|
|
52
|
+
global.gc?.();
|
|
47
53
|
durations[i++] = duration;
|
|
48
54
|
const delta = duration - mean;
|
|
49
55
|
mean += delta / BigInt(i);
|
package/build/runner.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runner.ts"],"sourcesContent":["import { Options, Control } from './types.js';\n\nconst COMPLETE_VALUE = 100_00;\n\nconst runSync = (run: Function) => {\n return (...args: unknown[]) => {\n const start = process.hrtime.bigint();\n run(...args);\n return process.hrtime.bigint() - start;\n };\n};\n\nconst runAsync = (run: Function) => {\n return async (...args: unknown[]) => {\n const start = process.hrtime.bigint();\n await run(...args);\n return process.hrtime.bigint() - start;\n };\n};\n\nexport const benchmark = async <TContext, TInput>({\n setup,\n teardown,\n pre,\n run: runRaw,\n post,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n}: Required<Options<TContext, TInput>>) => {\n const durations = new BigUint64Array(durationsSAB);\n const control = new Int32Array(controlSAB);\n\n control[Control.INDEX] = 0;\n control[Control.PROGRESS] = 0;\n control[Control.COMPLETE] = 255;\n\n const context = (await setup?.()) as TContext;\n const maxCycles = durations.length;\n\n try {\n await pre?.(context, data!);\n const result = runRaw(context, data!);\n await post?.(context, data!);\n const run = result instanceof Promise ? runAsync(runRaw) : runSync(runRaw);\n const start = Date.now();\n while (Date.now() - start < 1_000) {\n Math.sqrt(Math.random());\n }\n for (let i = 0; i < warmupCycles; i++) {\n await pre?.(context, data!);\n await run(context, data);\n await post?.(context, data!);\n }\n\n let i = 0;\n let mean = 0n;\n let m2 = 0n;\n\n while (true) {\n if (i >= maxCycles) break;\n\n await pre?.(context, data!);\n const duration = await run(context, data);\n await post?.(context, data!);\n\n durations[i++] = duration;\n const delta = duration - mean;\n mean += delta / BigInt(i);\n m2 += delta * (duration - mean);\n\n const progress = Math.max(i / maxCycles) * COMPLETE_VALUE;\n control[Control.PROGRESS] = progress;\n\n if (i >= minCycles) {\n const variance = Number(m2) / (i - 1);\n const stddev = Math.sqrt(variance);\n if (stddev <= Number(absThreshold)) {\n break;\n }\n\n const meanNum = Number(mean);\n const cov = stddev / (meanNum || 1);\n if (cov <= relThreshold) {\n break;\n }\n }\n }\n\n control[Control.INDEX] = i;\n control[Control.COMPLETE] = 0;\n } catch (e) {\n console.error(e && typeof e === 'object' && 'stack' in e ? e.stack : e);\n control[Control.COMPLETE] = 1;\n } finally {\n try {\n await teardown?.(context);\n } catch (e) {\n control[Control.COMPLETE] = 2;\n console.error(e && typeof e === 'object' && 'stack' in e ? e.stack : e);\n }\n }\n\n return control[Control.COMPLETE];\n};\n"],"names":["Control","COMPLETE_VALUE","runSync","run","args","start","process","hrtime","bigint","runAsync","benchmark","setup","teardown","pre","runRaw","post","data","warmupCycles","minCycles","absThreshold","relThreshold","durationsSAB","controlSAB","durations","BigUint64Array","control","Int32Array","INDEX","PROGRESS","COMPLETE","context","maxCycles","length","result","Promise","Date","now","Math","sqrt","random","i","mean","m2","duration","delta","BigInt","progress","max","variance","Number","stddev","meanNum","cov","e","console","error","stack"],"mappings":"AAAA,SAAkBA,OAAO,QAAQ,aAAa;AAE9C,MAAMC,iBAAiB;AAEvB,MAAMC,UAAU,CAACC;IACf,OAAO,CAAC,GAAGC;QACT,MAAMC,QAAQC,QAAQC,MAAM,CAACC,MAAM;QACnCL,OAAOC;QACP,OAAOE,QAAQC,MAAM,CAACC,MAAM,KAAKH;IACnC;AACF;AAEA,MAAMI,WAAW,CAACN;IAChB,OAAO,OAAO,GAAGC;QACf,MAAMC,QAAQC,QAAQC,MAAM,CAACC,MAAM;QACnC,MAAML,OAAOC;QACb,OAAOE,QAAQC,MAAM,CAACC,MAAM,KAAKH;IACnC;AACF;AAEA,OAAO,MAAMK,YAAY,OAAyB,EAChDC,KAAK,EACLC,QAAQ,EACRC,GAAG,EACHV,KAAKW,MAAM,EACXC,IAAI,EACJC,IAAI,EAEJC,YAAY,EACZC,SAAS,EACTC,YAAY,EACZC,YAAY,EAEZC,YAAY,EACZC,UAAU,EAC0B;IACpC,MAAMC,YAAY,IAAIC,eAAeH;IACrC,MAAMI,UAAU,IAAIC,WAAWJ;IAE/BG,OAAO,CAACzB,QAAQ2B,KAAK,CAAC,GAAG;IACzBF,OAAO,CAACzB,QAAQ4B,QAAQ,CAAC,GAAG;IAC5BH,OAAO,CAACzB,QAAQ6B,QAAQ,CAAC,GAAG;IAE5B,MAAMC,UAAW,MAAMnB;IACvB,MAAMoB,YAAYR,UAAUS,MAAM;IAElC,IAAI;QACF,MAAMnB,MAAMiB,SAASd;QACrB,MAAMiB,SAASnB,OAAOgB,SAASd;QAC/B,MAAMD,OAAOe,SAASd;
|
|
1
|
+
{"version":3,"sources":["../src/runner.ts"],"sourcesContent":["import { Options, Control } from './types.js';\n\nconst COMPLETE_VALUE = 100_00;\n\nconst runSync = (run: Function) => {\n return (...args: unknown[]) => {\n const start = process.hrtime.bigint();\n run(...args);\n return process.hrtime.bigint() - start;\n };\n};\n\nconst runAsync = (run: Function) => {\n return async (...args: unknown[]) => {\n const start = process.hrtime.bigint();\n await run(...args);\n return process.hrtime.bigint() - start;\n };\n};\n\nexport const benchmark = async <TContext, TInput>({\n setup,\n teardown,\n pre,\n run: runRaw,\n post,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n}: Required<Options<TContext, TInput>>) => {\n const durations = new BigUint64Array(durationsSAB);\n const control = new Int32Array(controlSAB);\n\n control[Control.INDEX] = 0;\n control[Control.PROGRESS] = 0;\n control[Control.COMPLETE] = 255;\n\n const context = (await setup?.()) as TContext;\n const maxCycles = durations.length;\n\n try {\n await pre?.(context, data!);\n const result = runRaw(context, data!);\n await post?.(context, data!);\n global.gc?.();\n global.gc?.();\n\n const run = result instanceof Promise ? runAsync(runRaw) : runSync(runRaw);\n const start = Date.now();\n while (Date.now() - start < 1_000) {\n Math.sqrt(Math.random());\n }\n for (let i = 0; i < warmupCycles; i++) {\n await pre?.(context, data!);\n await run(context, data);\n await post?.(context, data!);\n global.gc?.();\n global.gc?.();\n }\n\n let i = 0;\n let mean = 0n;\n let m2 = 0n;\n\n while (true) {\n if (i >= maxCycles) break;\n\n await pre?.(context, data!);\n const duration = await run(context, data);\n await post?.(context, data!);\n global.gc?.();\n global.gc?.();\n\n durations[i++] = duration;\n const delta = duration - mean;\n mean += delta / BigInt(i);\n m2 += delta * (duration - mean);\n\n const progress = Math.max(i / maxCycles) * COMPLETE_VALUE;\n control[Control.PROGRESS] = progress;\n\n if (i >= minCycles) {\n const variance = Number(m2) / (i - 1);\n const stddev = Math.sqrt(variance);\n if (stddev <= Number(absThreshold)) {\n break;\n }\n\n const meanNum = Number(mean);\n const cov = stddev / (meanNum || 1);\n if (cov <= relThreshold) {\n break;\n }\n }\n }\n\n control[Control.INDEX] = i;\n control[Control.COMPLETE] = 0;\n } catch (e) {\n console.error(e && typeof e === 'object' && 'stack' in e ? e.stack : e);\n control[Control.COMPLETE] = 1;\n } finally {\n try {\n await teardown?.(context);\n } catch (e) {\n control[Control.COMPLETE] = 2;\n console.error(e && typeof e === 'object' && 'stack' in e ? e.stack : e);\n }\n }\n\n return control[Control.COMPLETE];\n};\n"],"names":["Control","COMPLETE_VALUE","runSync","run","args","start","process","hrtime","bigint","runAsync","benchmark","setup","teardown","pre","runRaw","post","data","warmupCycles","minCycles","absThreshold","relThreshold","durationsSAB","controlSAB","durations","BigUint64Array","control","Int32Array","INDEX","PROGRESS","COMPLETE","context","maxCycles","length","result","global","gc","Promise","Date","now","Math","sqrt","random","i","mean","m2","duration","delta","BigInt","progress","max","variance","Number","stddev","meanNum","cov","e","console","error","stack"],"mappings":"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"}
|
package/build/worker.cjs
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
+
Object.defineProperty(exports, "exitCode", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return exitCode;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
5
11
|
const _nodeworker_threads = require("node:worker_threads");
|
|
6
12
|
const _runnercjs = require("./runner.cjs");
|
|
7
13
|
const { setupCode, teardownCode, preCode, runCode, postCode, data, warmupCycles, minCycles, absThreshold, relThreshold, durationsSAB, controlSAB } = _nodeworker_threads.workerData;
|
package/build/worker.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { benchmark } from './runner.js';\nimport { SetupFn, TeardownFn, StepFn, WorkerOptions } from './types.js';\n\nconst {\n setupCode,\n teardownCode,\n preCode,\n runCode,\n postCode,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n}: WorkerOptions = workerData;\n\nconst setup: SetupFn<unknown> = setupCode && Function(`return ${setupCode};`)();\nconst teardown: TeardownFn<unknown> = teardownCode && Function(`return ${teardownCode};`)();\n\nconst pre: StepFn<unknown, unknown> = preCode && Function(`return ${preCode};`)();\nconst run: StepFn<unknown, unknown> = runCode && Function(`return ${runCode};`)();\nconst post: StepFn<unknown, unknown> = postCode && Function(`return ${postCode};`)();\n\
|
|
1
|
+
{"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { benchmark } from './runner.js';\nimport { SetupFn, TeardownFn, StepFn, WorkerOptions } from './types.js';\n\nconst {\n setupCode,\n teardownCode,\n preCode,\n runCode,\n postCode,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n}: WorkerOptions = workerData;\n\nconst setup: SetupFn<unknown> = setupCode && Function(`return ${setupCode};`)();\nconst teardown: TeardownFn<unknown> = teardownCode && Function(`return ${teardownCode};`)();\n\nconst pre: StepFn<unknown, unknown> = preCode && Function(`return ${preCode};`)();\nconst run: StepFn<unknown, unknown> = runCode && Function(`return ${runCode};`)();\nconst post: StepFn<unknown, unknown> = postCode && Function(`return ${postCode};`)();\n\nexport const 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\n durationsSAB,\n controlSAB,\n});\n\nprocess.exit(exitCode);\n"],"names":["exitCode","setupCode","teardownCode","preCode","runCode","postCode","data","warmupCycles","minCycles","absThreshold","relThreshold","durationsSAB","controlSAB","workerData","setup","Function","teardown","pre","run","post","benchmark","process","exit"],"mappings":";;;;+BA4BaA;;;eAAAA;;;oCA5Bc;2BACD;AAG1B,MAAM,EACJC,SAAS,EACTC,YAAY,EACZC,OAAO,EACPC,OAAO,EACPC,QAAQ,EACRC,IAAI,EAEJC,YAAY,EACZC,SAAS,EACTC,YAAY,EACZC,YAAY,EAEZC,YAAY,EACZC,UAAU,EACX,GAAkBC,8BAAU;AAE7B,MAAMC,QAA0Bb,aAAac,SAAS,CAAC,OAAO,EAAEd,UAAU,CAAC,CAAC;AAC5E,MAAMe,WAAgCd,gBAAgBa,SAAS,CAAC,OAAO,EAAEb,aAAa,CAAC,CAAC;AAExF,MAAMe,MAAgCd,WAAWY,SAAS,CAAC,OAAO,EAAEZ,QAAQ,CAAC,CAAC;AAC9E,MAAMe,MAAgCd,WAAWW,SAAS,CAAC,OAAO,EAAEX,QAAQ,CAAC,CAAC;AAC9E,MAAMe,OAAiCd,YAAYU,SAAS,CAAC,OAAO,EAAEV,SAAS,CAAC,CAAC;AAE1E,MAAML,WAAW,MAAMoB,IAAAA,oBAAS,EAAC;IACtCN;IACAE;IACAC;IACAC;IACAC;IACAb;IAEAC;IACAC;IACAC;IACAC;IAEAC;IACAC;AACF;AAEAS,QAAQC,IAAI,CAACtB"}
|
package/build/worker.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare const exitCode: number;
|
package/build/worker.js
CHANGED
|
@@ -6,7 +6,7 @@ const teardown = teardownCode && Function(`return ${teardownCode};`)();
|
|
|
6
6
|
const pre = preCode && Function(`return ${preCode};`)();
|
|
7
7
|
const run = runCode && Function(`return ${runCode};`)();
|
|
8
8
|
const post = postCode && Function(`return ${postCode};`)();
|
|
9
|
-
const exitCode = await benchmark({
|
|
9
|
+
export const exitCode = await benchmark({
|
|
10
10
|
setup,
|
|
11
11
|
teardown,
|
|
12
12
|
pre,
|
package/build/worker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { benchmark } from './runner.js';\nimport { SetupFn, TeardownFn, StepFn, WorkerOptions } from './types.js';\n\nconst {\n setupCode,\n teardownCode,\n preCode,\n runCode,\n postCode,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n}: WorkerOptions = workerData;\n\nconst setup: SetupFn<unknown> = setupCode && Function(`return ${setupCode};`)();\nconst teardown: TeardownFn<unknown> = teardownCode && Function(`return ${teardownCode};`)();\n\nconst pre: StepFn<unknown, unknown> = preCode && Function(`return ${preCode};`)();\nconst run: StepFn<unknown, unknown> = runCode && Function(`return ${runCode};`)();\nconst post: StepFn<unknown, unknown> = postCode && Function(`return ${postCode};`)();\n\
|
|
1
|
+
{"version":3,"sources":["../src/worker.ts"],"sourcesContent":["import { workerData } from 'node:worker_threads';\nimport { benchmark } from './runner.js';\nimport { SetupFn, TeardownFn, StepFn, WorkerOptions } from './types.js';\n\nconst {\n setupCode,\n teardownCode,\n preCode,\n runCode,\n postCode,\n data,\n\n warmupCycles,\n minCycles,\n absThreshold,\n relThreshold,\n\n durationsSAB,\n controlSAB,\n}: WorkerOptions = workerData;\n\nconst setup: SetupFn<unknown> = setupCode && Function(`return ${setupCode};`)();\nconst teardown: TeardownFn<unknown> = teardownCode && Function(`return ${teardownCode};`)();\n\nconst pre: StepFn<unknown, unknown> = preCode && Function(`return ${preCode};`)();\nconst run: StepFn<unknown, unknown> = runCode && Function(`return ${runCode};`)();\nconst post: StepFn<unknown, unknown> = postCode && Function(`return ${postCode};`)();\n\nexport const 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\n durationsSAB,\n controlSAB,\n});\n\nprocess.exit(exitCode);\n"],"names":["workerData","benchmark","setupCode","teardownCode","preCode","runCode","postCode","data","warmupCycles","minCycles","absThreshold","relThreshold","durationsSAB","controlSAB","setup","Function","teardown","pre","run","post","exitCode","process","exit"],"mappings":"AAAA,SAASA,UAAU,QAAQ,sBAAsB;AACjD,SAASC,SAAS,QAAQ,cAAc;AAGxC,MAAM,EACJC,SAAS,EACTC,YAAY,EACZC,OAAO,EACPC,OAAO,EACPC,QAAQ,EACRC,IAAI,EAEJC,YAAY,EACZC,SAAS,EACTC,YAAY,EACZC,YAAY,EAEZC,YAAY,EACZC,UAAU,EACX,GAAkBb;AAEnB,MAAMc,QAA0BZ,aAAaa,SAAS,CAAC,OAAO,EAAEb,UAAU,CAAC,CAAC;AAC5E,MAAMc,WAAgCb,gBAAgBY,SAAS,CAAC,OAAO,EAAEZ,aAAa,CAAC,CAAC;AAExF,MAAMc,MAAgCb,WAAWW,SAAS,CAAC,OAAO,EAAEX,QAAQ,CAAC,CAAC;AAC9E,MAAMc,MAAgCb,WAAWU,SAAS,CAAC,OAAO,EAAEV,QAAQ,CAAC,CAAC;AAC9E,MAAMc,OAAiCb,YAAYS,SAAS,CAAC,OAAO,EAAET,SAAS,CAAC,CAAC;AAEjF,OAAO,MAAMc,WAAW,MAAMnB,UAAU;IACtCa;IACAE;IACAC;IACAC;IACAC;IACAZ;IAEAC;IACAC;IACAC;IACAC;IAEAC;IACAC;AACF,GAAG;AAEHQ,QAAQC,IAAI,CAACF"}
|
package/examples/complete.ts
CHANGED
|
@@ -29,7 +29,7 @@ v8Target
|
|
|
29
29
|
// executed before measurement
|
|
30
30
|
})
|
|
31
31
|
.post(async (_ctx, _input) => {
|
|
32
|
-
// executed
|
|
32
|
+
// executed after measurement
|
|
33
33
|
});
|
|
34
34
|
|
|
35
35
|
v8Target
|
|
@@ -64,11 +64,11 @@ jsonTarget
|
|
|
64
64
|
// executed before measurement
|
|
65
65
|
})
|
|
66
66
|
.post(async (_ctx, _input) => {
|
|
67
|
-
// executed
|
|
67
|
+
// executed after measurement
|
|
68
68
|
});
|
|
69
69
|
|
|
70
70
|
jsonTarget
|
|
71
|
-
.measure('
|
|
71
|
+
.measure('parse', ({ parse, serialized }) => {
|
|
72
72
|
parse(serialized);
|
|
73
73
|
})
|
|
74
74
|
.pre(async (ctx, input) => {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Demonstrates custom report types and statistics
|
|
2
|
+
// CLI mode: npx overtake examples/custom-reports.ts -f table -r ops mean median p95 p99
|
|
3
|
+
// Programmatic mode: node examples/custom-reports.js
|
|
4
|
+
|
|
5
|
+
import { Benchmark, printTableReports } from '../build/index.js';
|
|
6
|
+
|
|
7
|
+
const performanceSuite = new Benchmark('10K numbers', () => Array.from({ length: 10_000 }, () => Math.random() * 1000));
|
|
8
|
+
|
|
9
|
+
// Compare different rounding methods
|
|
10
|
+
performanceSuite.target('rounding methods').measure('Math.floor', (_, numbers) => {
|
|
11
|
+
for (const n of numbers) Math.floor(n);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Execute with custom statistics
|
|
15
|
+
const reports = await performanceSuite.execute({
|
|
16
|
+
workers: 4,
|
|
17
|
+
minCycles: 100,
|
|
18
|
+
maxCycles: 500,
|
|
19
|
+
reportTypes: ['ops', 'mean', 'median', 'p50', 'p95', 'p99', 'min', 'max'] as const,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
printTableReports(reports);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Demonstrates correct import patterns for worker context
|
|
2
|
+
// Run: npx overtake examples/imports.ts
|
|
3
|
+
|
|
4
|
+
const importExamples = benchmark('test data', () => Buffer.from('hello world'));
|
|
5
|
+
|
|
6
|
+
// Node built-in modules work normally
|
|
7
|
+
importExamples
|
|
8
|
+
.target('node built-ins', async () => {
|
|
9
|
+
const { createHash } = await import('node:crypto');
|
|
10
|
+
return { createHash };
|
|
11
|
+
})
|
|
12
|
+
.measure('sha256 hash', ({ createHash }, buffer) => {
|
|
13
|
+
createHash('sha256').update(buffer).digest('hex');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// IMPORTANT: Local files need absolute paths
|
|
17
|
+
// Relative imports resolve from node_modules/overtake/build/, not your project
|
|
18
|
+
const localFilesTarget = importExamples.target('local files', async () => {
|
|
19
|
+
const { join } = await import('node:path');
|
|
20
|
+
|
|
21
|
+
// Build absolute path to your local module
|
|
22
|
+
const localModulePath = join(process.cwd(), './build/types.js');
|
|
23
|
+
const { DEFAULT_CYCLES, Control } = await import(localModulePath);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
DEFAULT_CYCLES,
|
|
27
|
+
Control,
|
|
28
|
+
// You can also define functions here
|
|
29
|
+
processData: (data: Buffer) => data.length,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
localFilesTarget.measure('use imported constant', ({ DEFAULT_CYCLES }) => {
|
|
34
|
+
DEFAULT_CYCLES > 100;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
localFilesTarget.measure('use local function', ({ processData }, buffer) => {
|
|
38
|
+
processData(buffer);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Memory management pattern - prevent GC during measurements
|
|
42
|
+
importExamples
|
|
43
|
+
.target('memory management', () => {
|
|
44
|
+
const gcBlock = new Set();
|
|
45
|
+
return { gcBlock };
|
|
46
|
+
})
|
|
47
|
+
.measure('without GC', ({ gcBlock }, buffer) => {
|
|
48
|
+
const result = Buffer.concat([buffer, buffer]);
|
|
49
|
+
gcBlock.add(result); // Keep reference alive
|
|
50
|
+
result.length;
|
|
51
|
+
});
|
package/examples/quick-start.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
//
|
|
2
|
-
// npx overtake examples/quick-start.ts
|
|
1
|
+
// Minimal example - comparing array sum algorithms
|
|
2
|
+
// Run: npx overtake examples/quick-start.ts
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const sumBenchmark = benchmark('1M numbers', () => Array.from({ length: 1_000_000 }, (_, index) => index));
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
const n = input.length;
|
|
6
|
+
sumBenchmark.target('for loop').measure('sum', (_, numbers) => {
|
|
8
7
|
let sum = 0;
|
|
9
|
-
for (let i = 0; i <
|
|
10
|
-
sum +=
|
|
8
|
+
for (let i = 0; i < numbers.length; i++) {
|
|
9
|
+
sum += numbers[i];
|
|
11
10
|
}
|
|
12
11
|
});
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
sumBenchmark.target('reduce').measure('sum', (_, numbers) => {
|
|
14
|
+
numbers.reduce((accumulator, current) => accumulator + current, 0);
|
|
16
15
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overtake",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "NodeJS performance benchmark",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -45,16 +45,16 @@
|
|
|
45
45
|
"@swc/jest": "^0.2.39",
|
|
46
46
|
"@types/async": "^3.2.25",
|
|
47
47
|
"@types/jest": "^30.0.0",
|
|
48
|
-
"@types/node": "^24.
|
|
48
|
+
"@types/node": "^24.3.0",
|
|
49
49
|
"husky": "^9.1.7",
|
|
50
|
-
"inop": "^0.7.
|
|
50
|
+
"inop": "^0.7.9",
|
|
51
51
|
"jest": "^30.0.5",
|
|
52
52
|
"prettier": "^3.6.2",
|
|
53
53
|
"pretty-quick": "^4.2.2",
|
|
54
54
|
"typescript": "^5.9.2"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@swc/core": "^1.13.
|
|
57
|
+
"@swc/core": "^1.13.5",
|
|
58
58
|
"async": "^3.2.6",
|
|
59
59
|
"commander": "^14.0.0",
|
|
60
60
|
"glob": "^11.0.3"
|
package/src/cli.ts
CHANGED
|
@@ -59,7 +59,10 @@ commander
|
|
|
59
59
|
return instance;
|
|
60
60
|
};
|
|
61
61
|
const script = new SourceTextModule(code, {
|
|
62
|
-
context: createContext({
|
|
62
|
+
context: createContext({
|
|
63
|
+
benchmark,
|
|
64
|
+
Buffer,
|
|
65
|
+
}),
|
|
63
66
|
});
|
|
64
67
|
const imports = new Map();
|
|
65
68
|
await script.link(async (specifier: string, referencingModule) => {
|
package/src/runner.ts
CHANGED
|
@@ -48,6 +48,9 @@ export const benchmark = async <TContext, TInput>({
|
|
|
48
48
|
await pre?.(context, data!);
|
|
49
49
|
const result = runRaw(context, data!);
|
|
50
50
|
await post?.(context, data!);
|
|
51
|
+
global.gc?.();
|
|
52
|
+
global.gc?.();
|
|
53
|
+
|
|
51
54
|
const run = result instanceof Promise ? runAsync(runRaw) : runSync(runRaw);
|
|
52
55
|
const start = Date.now();
|
|
53
56
|
while (Date.now() - start < 1_000) {
|
|
@@ -57,6 +60,8 @@ export const benchmark = async <TContext, TInput>({
|
|
|
57
60
|
await pre?.(context, data!);
|
|
58
61
|
await run(context, data);
|
|
59
62
|
await post?.(context, data!);
|
|
63
|
+
global.gc?.();
|
|
64
|
+
global.gc?.();
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
let i = 0;
|
|
@@ -69,6 +74,8 @@ export const benchmark = async <TContext, TInput>({
|
|
|
69
74
|
await pre?.(context, data!);
|
|
70
75
|
const duration = await run(context, data);
|
|
71
76
|
await post?.(context, data!);
|
|
77
|
+
global.gc?.();
|
|
78
|
+
global.gc?.();
|
|
72
79
|
|
|
73
80
|
durations[i++] = duration;
|
|
74
81
|
const delta = duration - mean;
|
package/src/worker.ts
CHANGED
|
@@ -26,7 +26,7 @@ const pre: StepFn<unknown, unknown> = preCode && Function(`return ${preCode};`)(
|
|
|
26
26
|
const run: StepFn<unknown, unknown> = runCode && Function(`return ${runCode};`)();
|
|
27
27
|
const post: StepFn<unknown, unknown> = postCode && Function(`return ${postCode};`)();
|
|
28
28
|
|
|
29
|
-
const exitCode = await benchmark({
|
|
29
|
+
export const exitCode = await benchmark({
|
|
30
30
|
setup,
|
|
31
31
|
teardown,
|
|
32
32
|
pre,
|
package/CLAUDE.md
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
## Project Overview
|
|
6
|
-
|
|
7
|
-
Overtake is a high-precision JavaScript/TypeScript benchmarking library that uses worker thread isolation and statistical convergence to provide accurate performance measurements. It solves common benchmarking problems like JIT optimization interference and cross-benchmark contamination.
|
|
8
|
-
|
|
9
|
-
## Development Commands
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
# Build the project (uses inop for transpilation + tsc for declarations only)
|
|
13
|
-
npm run build
|
|
14
|
-
# or with pnpm
|
|
15
|
-
pnpm build
|
|
16
|
-
|
|
17
|
-
# Run tests (uses Jest with SWC transpilation - no test files currently exist)
|
|
18
|
-
npm test
|
|
19
|
-
|
|
20
|
-
# Execute benchmarks via CLI
|
|
21
|
-
npx overtake "examples/*.ts" -f table -r ops mean p95
|
|
22
|
-
|
|
23
|
-
# Run a single benchmark file
|
|
24
|
-
npx overtake examples/quick-start.ts
|
|
25
|
-
|
|
26
|
-
# Start the CLI directly (requires argument)
|
|
27
|
-
npm start examples/quick-start.ts
|
|
28
|
-
|
|
29
|
-
# Format code with Prettier (via pre-commit hook)
|
|
30
|
-
npx prettier --write "src/**/*.ts" "examples/**/*.ts"
|
|
31
|
-
|
|
32
|
-
# Note: Package manager is pnpm@10.14.0 (see packageManager field in package.json)
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Architecture
|
|
36
|
-
|
|
37
|
-
### Core Components
|
|
38
|
-
|
|
39
|
-
1. **Benchmark Class** (`src/index.ts`): Main API with fluent interface for defining benchmarks using feed → target → measure pattern
|
|
40
|
-
2. **Executor** (`src/executor.ts`): Worker thread pool management using async queues and SharedArrayBuffer for zero-copy communication
|
|
41
|
-
3. **Worker** (`src/worker.ts`): Isolated execution environment with fresh V8 context per benchmark
|
|
42
|
-
4. **Runner** (`src/runner.ts`): Handles warmup cycles, statistical convergence detection, and timing collection
|
|
43
|
-
5. **Reporter** (`src/reporter.ts`): Statistical analysis and result formatting (ops/sec, percentiles, mean/median/mode)
|
|
44
|
-
|
|
45
|
-
### Key Design Patterns
|
|
46
|
-
|
|
47
|
-
- **Worker Thread Isolation**: Each benchmark runs in separate thread to prevent contamination
|
|
48
|
-
- **SharedArrayBuffer Communication**: Zero-copy data transfer for high-precision bigint timing
|
|
49
|
-
- **Statistical Convergence**: Automatic cycle adjustment based on configurable confidence thresholds
|
|
50
|
-
- **Dynamic Code Execution**: Function serialization across threads with VM module sandboxing
|
|
51
|
-
|
|
52
|
-
### API Structure
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
// Fluent API pattern
|
|
56
|
-
benchmark('name', feedFunction)
|
|
57
|
-
.target('implementation')
|
|
58
|
-
.measure('operation', measureFunction)
|
|
59
|
-
.execute() // Returns Promise<Report[]>
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Technical Requirements
|
|
63
|
-
|
|
64
|
-
- Node.js >=22 (uses modern features like VM modules)
|
|
65
|
-
- ES modules only (no CommonJS)
|
|
66
|
-
- TypeScript with ESNext target
|
|
67
|
-
- Uses SWC for transpilation (not tsc for builds)
|
|
68
|
-
|
|
69
|
-
## Important Implementation Notes
|
|
70
|
-
|
|
71
|
-
- **CRITICAL**: All imports must be dynamic inside target callbacks since they run in worker threads:
|
|
72
|
-
```typescript
|
|
73
|
-
// CORRECT - dynamic import inside target
|
|
74
|
-
.target('V8', async () => {
|
|
75
|
-
const { serialize } = await import('node:v8');
|
|
76
|
-
return { serialize };
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
// WRONG - static import at top level
|
|
80
|
-
import { serialize } from 'node:v8';
|
|
81
|
-
.target('V8', () => ({ serialize }))
|
|
82
|
-
```
|
|
83
|
-
- Timing uses `process.hrtime.bigint()` for nanosecond precision
|
|
84
|
-
- Worker threads communicate via SharedArrayBuffer to minimize overhead
|
|
85
|
-
- Build process uses `inop` tool for transpilation followed by tsc for declarations only
|
|
86
|
-
- Test files should match `*.spec.ts` or `*.test.ts` patterns (currently no tests exist)
|
|
87
|
-
|
|
88
|
-
## API Usage Modes
|
|
89
|
-
|
|
90
|
-
### CLI Mode (Global `benchmark` function)
|
|
91
|
-
- Used when running via `npx overtake file.ts`
|
|
92
|
-
- CLI provides global `benchmark` function automatically
|
|
93
|
-
- No imports needed, no `.execute()` call required
|
|
94
|
-
- Results printed based on CLI flags
|
|
95
|
-
|
|
96
|
-
Example (examples/quick-start.ts):
|
|
97
|
-
```typescript
|
|
98
|
-
const suite = benchmark('name', () => data);
|
|
99
|
-
suite.target('impl').measure('op', (ctx, input) => { /* ... */ });
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Programmatic Mode (Import Benchmark class)
|
|
103
|
-
- Used for standalone scripts with custom execution
|
|
104
|
-
- Must import Benchmark class and printer functions
|
|
105
|
-
- Must call `.execute()` and handle results
|
|
106
|
-
|
|
107
|
-
Example (examples/complete.ts):
|
|
108
|
-
```typescript
|
|
109
|
-
import { Benchmark, printJSONReports } from '../build/index.js';
|
|
110
|
-
const suite = new Benchmark('name', () => data);
|
|
111
|
-
// ... define targets and measures
|
|
112
|
-
const reports = await suite.execute({ /* options */ });
|
|
113
|
-
printJSONReports(reports, 2);
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Common Issues and Solutions
|
|
117
|
-
|
|
118
|
-
### TypeScript Transpilation
|
|
119
|
-
- CLI uses SWC's `transform` function to strip TypeScript types
|
|
120
|
-
- If you see "Missing initializer in const declaration", it means TypeScript wasn't transpiled
|
|
121
|
-
- The transpiler in `src/cli.ts` must use `transform`, not `parse`/`print`
|
|
122
|
-
|
|
123
|
-
### Benchmarks Not Running
|
|
124
|
-
- CLI mode: Ensure using global `benchmark` function (no import)
|
|
125
|
-
- Programmatic mode: Must call `.execute()` and handle results
|
|
126
|
-
- Check that worker count doesn't exceed CPU cores
|
|
127
|
-
|
|
128
|
-
### Memory Management Patterns
|
|
129
|
-
- Use `gcBlock` Set to prevent garbage collection during measurements:
|
|
130
|
-
```typescript
|
|
131
|
-
.target('name', () => {
|
|
132
|
-
const gcBlock = new Set(); // Prevents GC
|
|
133
|
-
return { gcBlock };
|
|
134
|
-
})
|
|
135
|
-
.measure('op', ({ gcBlock }, input) => {
|
|
136
|
-
gcBlock.add(result); // Keep reference alive
|
|
137
|
-
})
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
## Code Quality Tools
|
|
141
|
-
|
|
142
|
-
- **Formatter**: Prettier (config in `.prettierrc`) - runs automatically on commit via husky pre-commit hook
|
|
143
|
-
- **Test Runner**: Jest with SWC transpilation (config in `jest.config.js`)
|
|
144
|
-
- **TypeScript Config**: Strict mode enabled, targets ESNext with NodeNext modules
|
|
145
|
-
- **No linter or type-check commands**: Add these if needed in the future
|
package/examples/array-copy.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
const copySuite = benchmark('1M array of strings', () => Array.from({ length: 1_000_000 }, (_, idx) => `${idx}`))
|
|
2
|
-
.feed('1M array of numbers', () => Array.from({ length: 1_000_000 }, (_, idx) => idx))
|
|
3
|
-
.feed('1M typed array', () => new Uint32Array(1_000_000).map((_, idx) => idx));
|
|
4
|
-
|
|
5
|
-
copySuite.target('for loop').measure('copy half', (_, input) => {
|
|
6
|
-
const n = input?.length ?? 0;
|
|
7
|
-
const mid = n / 2;
|
|
8
|
-
for (let i = 0; i < mid; i++) {
|
|
9
|
-
input[i + mid] = input[i];
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
copySuite.target('copyWithin').measure('copy half', (_, input) => {
|
|
14
|
-
const n = input?.length ?? 0;
|
|
15
|
-
const mid = n / 2;
|
|
16
|
-
input.copyWithin(mid, 0, mid);
|
|
17
|
-
});
|
package/examples/object-merge.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { Benchmark, printSimpleReports } from '../build/index.js';
|
|
2
|
-
|
|
3
|
-
const objectMergeSuite = new Benchmark('1K array of objects', () => Array.from({ length: 1_000 }, (_, idx) => ({ [idx]: idx })));
|
|
4
|
-
|
|
5
|
-
objectMergeSuite.target('reduce destructure').measure('data', (_, input) => {
|
|
6
|
-
input.reduce((acc, obj) => {
|
|
7
|
-
return { ...acc, ...obj };
|
|
8
|
-
}, {});
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
objectMergeSuite.target('reduce assign').measure('data', (_, input) => {
|
|
12
|
-
input.reduce((acc, obj) => {
|
|
13
|
-
Object.assign(acc, obj);
|
|
14
|
-
return acc;
|
|
15
|
-
}, {});
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
objectMergeSuite.target('forEach assign').measure('data', (_, input) => {
|
|
19
|
-
const result = {};
|
|
20
|
-
input.forEach((obj) => {
|
|
21
|
-
Object.assign(result, obj);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
objectMergeSuite.target('for assign').measure('data', (_, input) => {
|
|
26
|
-
const result = {};
|
|
27
|
-
for (let i = 0; i < input.length; i++) {
|
|
28
|
-
Object.assign(result, input[i]);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
objectMergeSuite.target('assign').measure('data', (_, input) => {
|
|
33
|
-
Object.assign({}, ...input);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const reports = await objectMergeSuite.execute({
|
|
37
|
-
reportTypes: ['ops'],
|
|
38
|
-
maxCycles: 10_000,
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
printSimpleReports(reports);
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
-
|
|
3
|
-
const serializeSuite = benchmark('1K strings', () => Array.from({ length: 10_000 }, () => randomUUID()));
|
|
4
|
-
|
|
5
|
-
const v8Target = serializeSuite.target('V8', async () => {
|
|
6
|
-
const { serialize, deserialize } = await import('node:v8');
|
|
7
|
-
const gcBlock = new Set();
|
|
8
|
-
return { serialize, deserialize, gcBlock };
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
v8Target.measure('serialize', ({ serialize, gcBlock }, input) => {
|
|
12
|
-
gcBlock.add(serialize(input));
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
serializeSuite
|
|
16
|
-
.target('JSON', () => {
|
|
17
|
-
const gcBlock = new Set();
|
|
18
|
-
return { gcBlock };
|
|
19
|
-
})
|
|
20
|
-
.measure('serialize', ({ gcBlock }, input) => {
|
|
21
|
-
gcBlock.add(JSON.stringify(input));
|
|
22
|
-
});
|