overtake 1.0.2 → 1.0.3

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/CLAUDE.md ADDED
@@ -0,0 +1,145 @@
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/README.md CHANGED
@@ -1,284 +1,262 @@
1
1
  # Overtake
2
2
 
3
- High-precision performance benchmarking library for Node.js with isolated worker thread execution and statistical convergence.
3
+ The fastest, most accurate JavaScript benchmarking library. Worker-isolated, statistically-rigorous, zero-overhead.
4
4
 
5
5
  [![Build Status][github-image]][github-url]
6
6
  [![NPM version][npm-image]][npm-url]
7
7
  [![Downloads][downloads-image]][npm-url]
8
8
 
9
- <!--[![Coverage Status][codecov-image]][codecov-url]-->
10
- <!--[![Maintainability][codeclimate-image]][codeclimate-url]-->
11
- <!--[![Snyk][snyk-image]][snyk-url]-->
9
+ ```bash
10
+ npm install -D overtake
11
+ ```
12
12
 
13
- ## Table of Contents
13
+ ## 5-Second Quick Start
14
14
 
15
- - [Why Overtake?](#why-overtake)
16
- - [Features](#features)
17
- - [Installing](#installing)
18
- - [Quick Start](#quick-start)
19
- - [API Guide](#api-guide)
20
- - [Examples](#examples)
21
- - [CLI Usage](#cli-usage)
22
- - [License](#license)
15
+ ```typescript
16
+ // benchmark.ts
17
+ const suite = benchmark('1M numbers', () => Array.from({ length: 1e6 }, (_, i) => i));
23
18
 
24
- ## Why Overtake?
19
+ suite.target('for loop').measure('sum', (_, arr) => {
20
+ let sum = 0;
21
+ for (let i = 0; i < arr.length; i++) sum += arr[i];
22
+ return sum;
23
+ });
24
+
25
+ suite.target('reduce').measure('sum', (_, arr) => arr.reduce((a, b) => a + b));
26
+ ```
25
27
 
26
- Traditional JavaScript benchmarking tools often suffer from:
28
+ ```bash
29
+ npx overtake benchmark.ts
30
+
31
+ # Output:
32
+ # for loop sum
33
+ # 1M numbers: 1,607 ops/s
34
+ #
35
+ # reduce sum
36
+ # 1M numbers: 238 ops/s (6.7x slower)
37
+ ```
27
38
 
28
- - **JIT optimization interference** - Code runs differently in benchmarks vs production
29
- - **Memory pressure artifacts** - GC pauses and memory allocation affect timing
30
- - **Cross-benchmark contamination** - Previous tests affect subsequent measurements
31
- - **Insufficient sample sizes** - Results vary wildly between runs
39
+ ## Why Overtake?
32
40
 
33
- Overtake solves these problems by:
41
+ **The Problem**: JavaScript benchmarks lie. JIT optimizations, garbage collection, and shared state make results meaningless.
34
42
 
35
- - **Worker thread isolation** - Each benchmark runs in a separate thread with fresh V8 context
36
- - **Statistical convergence** - Automatically runs until results are statistically stable
37
- - **Zero-copy result collection** - Uses SharedArrayBuffer to eliminate serialization overhead
38
- - **Proper warmup cycles** - Ensures JIT optimization before measurement
39
- - **Concurrent execution** - Runs multiple benchmarks in parallel for faster results
43
+ **The Solution**: Overtake runs every benchmark in an isolated worker thread with a fresh V8 context. No contamination. No lies.
40
44
 
41
- ## Features
45
+ | Feature | Overtake | Benchmark.js | Tinybench |
46
+ | ----------------------- | -------------------------- | ----------------- | ----------------- |
47
+ | Worker isolation | ✅ Each benchmark isolated | ❌ Shared context | ❌ Shared context |
48
+ | Active maintenance | ✅ 2025 | ❌ Archived 2017 | ✅ 2025 |
49
+ | Statistical convergence | ✅ Auto-adjusts cycles | ⚠️ Manual config | ⚠️ Manual config |
50
+ | Zero-copy timing | ✅ SharedArrayBuffer | ❌ Serialization | ❌ Serialization |
51
+ | TypeScript support | ✅ Built-in | ❌ Manual setup | ⚠️ Needs config |
42
52
 
43
- - 🚀 **Worker thread isolation** for accurate measurements
44
- - 📊 **Statistical convergence** with configurable confidence thresholds
45
- - 🔄 **Automatic warmup cycles** to stabilize JIT optimization
46
- - 💻 **TypeScript support** with transpilation built-in
47
- - 🎯 **Multiple comparison targets** in a single benchmark
48
- - 📈 **Rich statistics** including percentiles, mean, median, mode
49
- - 🖥️ **CLI and programmatic API**
50
- - ⚡ **Zero-copy communication** using SharedArrayBuffer
53
+ ## Core Concepts
51
54
 
52
- ## Installing
55
+ - **Feed**: Input data to benchmark (`'1M numbers'` → array of 1 million numbers)
56
+ - **Target**: Implementation variant (`'for loop'` vs `'reduce'`)
57
+ - **Measure**: Operation to time (`'sum'` operation)
58
+ - **Isolation**: Each benchmark runs in a separate worker thread with fresh V8 context
53
59
 
54
- Using pnpm:
60
+ ## Installation
55
61
 
56
62
  ```bash
57
- $ pnpm add -D overtake
63
+ # npm
64
+ npm install -D overtake
65
+
66
+ # pnpm
67
+ pnpm add -D overtake
68
+
69
+ # yarn
70
+ yarn add -D overtake
58
71
  ```
59
72
 
60
- Using npm:
73
+ ## ⚠️ Critical: Dynamic Imports Required
61
74
 
62
- ```bash
63
- $ npm install -D overtake
75
+ **Benchmarks run in isolated workers. Modules MUST be imported dynamically:**
76
+
77
+ ```typescript
78
+ // ❌ WRONG - Static import won't work in worker
79
+ import { serialize } from 'node:v8';
80
+ benchmark('data', getData).target('v8', () => ({ serialize })); // serialize is undefined!
81
+
82
+ // ✅ CORRECT - Dynamic import inside target
83
+ benchmark('data', getData)
84
+ .target('v8', async () => {
85
+ const { serialize } = await import('node:v8');
86
+ return { serialize };
87
+ })
88
+ .measure('serialize', ({ serialize }, input) => serialize(input));
64
89
  ```
65
90
 
66
- ## Quick Start
91
+ ## Usage
67
92
 
68
- ### Basic Benchmark
93
+ ### CLI Mode (Recommended)
69
94
 
70
- Compare different implementations of the same operation:
95
+ When using `npx overtake`, a global `benchmark` function is provided:
71
96
 
72
97
  ```typescript
73
- // benchmark.ts
74
- const suite = benchmark('Process 1000 items')
75
- .target('for loop')
76
- .measure('sum', (_, input) => {
77
- let sum = 0;
78
- for (let i = 0; i < 1000; i++) {
79
- sum += i;
80
- }
81
- return sum;
82
- });
83
-
84
- suite.target('reduce').measure('sum', (_, input) => {
85
- return Array.from({ length: 1000 }, (_, i) => i).reduce((a, b) => a + b, 0);
86
- });
98
+ // benchmark.ts - No imports needed!
99
+ benchmark('small', () => generateSmallData())
100
+ .feed('large', () => generateLargeData())
101
+ .target('algorithm A')
102
+ .measure('process', (_, input) => processA(input))
103
+ .target('algorithm B')
104
+ .measure('process', (_, input) => processB(input));
105
+
106
+ // No .execute() needed - CLI handles it
87
107
  ```
88
108
 
89
- Run with CLI:
90
-
91
109
  ```bash
92
- npx overtake benchmark.ts -f table
110
+ npx overtake benchmark.ts --format table
93
111
  ```
94
112
 
95
- ## API Guide
96
-
97
- ### Core Concepts
113
+ ### Programmatic Mode
98
114
 
99
- 1. **Benchmark**: The main container for your performance tests
100
- 2. **Feed**: Different input data sets to test with
101
- 3. **Target**: Different implementations to compare (e.g., "for loop" vs "reduce")
102
- 4. **Measure**: Specific operations to measure for each target
103
-
104
- ### Creating Benchmarks
115
+ For custom integration, import the Benchmark class:
105
116
 
106
117
  ```typescript
107
- // Create a benchmark with optional initial feed
108
- const suite = benchmark('Test name', () => generateInputData());
118
+ import { Benchmark, printTableReports } from 'overtake';
109
119
 
110
- // Add more input variations
111
- suite.feed('small dataset', () => generateSmallData()).feed('large dataset', () => generateLargeData());
120
+ const suite = new Benchmark('dataset', () => getData());
112
121
 
113
- // Define implementations to compare
114
- suite.target('implementation A').measure('operation', (ctx, input) => {
115
- // Your code here
116
- });
122
+ suite.target('impl').measure('op', (_, input) => process(input));
117
123
 
118
- suite.target('implementation B').measure('operation', (ctx, input) => {
119
- // Alternative implementation
124
+ // Must explicitly execute
125
+ const reports = await suite.execute({
126
+ workers: 4,
127
+ reportTypes: ['ops', 'mean', 'p95'],
120
128
  });
129
+
130
+ printTableReports(reports);
121
131
  ```
122
132
 
123
- ### Setup and Teardown
133
+ ## API Reference
134
+
135
+ ### Creating Benchmarks
124
136
 
125
137
  ```typescript
126
- suite
127
- .target('with setup', async () => {
128
- // Setup: runs once before measurements
129
- const connection = await createConnection();
130
- return { connection };
138
+ // Create with initial feed
139
+ benchmark('initial data', () => data)
140
+ .feed('more data', () => moreData) // Add more datasets
141
+
142
+ // Define what to compare
143
+ .target('implementation A')
144
+ .measure('operation', (ctx, input) => {
145
+ /* ... */
131
146
  })
132
- .measure('query', async (ctx, input) => {
133
- // ctx contains the setup return value
134
- await ctx.connection.query(input);
135
- })
136
- .teardown(async (ctx) => {
137
- // Cleanup: runs once after measurements
138
- await ctx.connection.close();
147
+
148
+ .target('implementation B')
149
+ .measure('operation', (ctx, input) => {
150
+ /* ... */
139
151
  });
140
152
  ```
141
153
 
142
- ### Pre and Post Hooks
154
+ ### Targets with Setup
143
155
 
144
156
  ```typescript
157
+ const suite = benchmark('data', () => Buffer.from('test data'));
158
+
145
159
  suite
146
- .target('with hooks')
147
- .measure('process', (ctx, input) => {
148
- processData(input);
149
- })
150
- .pre((ctx, input) => {
151
- // Runs before EACH measurement
152
- prepareData(input);
160
+ .target('with setup', async () => {
161
+ // Setup runs once before measurements
162
+ const { createHash } = await import('node:crypto');
163
+ const cache = new Map();
164
+ return { createHash, cache }; // Available as ctx in measure
153
165
  })
154
- .post((ctx, input) => {
155
- // Runs after EACH measurement
156
- cleanupData(input);
166
+ .measure('hash', ({ createHash, cache }, input) => {
167
+ // ctx contains setup return value
168
+ const hash = createHash('sha256').update(input).digest();
169
+ cache.set(input, hash);
170
+ return hash;
157
171
  });
158
172
  ```
159
173
 
160
- ## Examples
161
-
162
- ### Example 1: Array Operations Comparison
163
-
164
- This example compares different methods for copying array elements:
174
+ ### Preventing Garbage Collection
165
175
 
166
176
  ```typescript
167
- // array-copy.ts
168
- const suite = benchmark('1M array of strings', () => Array.from({ length: 1_000_000 }, (_, idx) => `${idx}`))
169
- .feed('1M array of numbers', () => Array.from({ length: 1_000_000 }, (_, idx) => idx))
170
- .feed('1M typed array', () => new Uint32Array(1_000_000).map((_, idx) => idx));
171
-
172
- suite.target('for loop').measure('copy half', (_, input) => {
173
- const n = input?.length ?? 0;
174
- const mid = n / 2;
175
- for (let i = 0; i < mid; i++) {
176
- input[i + mid] = input[i];
177
- }
178
- });
177
+ const suite = benchmark('data', () => [1, 2, 3, 4, 5]);
179
178
 
180
- suite.target('copyWithin').measure('copy half', (_, input) => {
181
- const n = input?.length ?? 0;
182
- const mid = n / 2;
183
- input.copyWithin(mid, 0, mid);
184
- });
179
+ suite
180
+ .target('no GC', () => {
181
+ const gcBlock = new Set(); // Keeps references alive
182
+ return { gcBlock };
183
+ })
184
+ .measure('process', ({ gcBlock }, input) => {
185
+ const result = input.map((x) => x * x);
186
+ gcBlock.add(result); // Prevent GC during measurement
187
+ return result;
188
+ });
185
189
  ```
186
190
 
187
- **Key insights from results:**
188
-
189
- - `copyWithin` is ~5x faster for typed arrays
190
- - `for loop` performs consistently across all array types
191
- - Regular arrays have different performance characteristics than typed arrays
192
-
193
- ### Example 2: Object Merging Strategies
191
+ ## Examples
194
192
 
195
- Compare different approaches to merge arrays of objects:
193
+ ### Serialization Comparison
196
194
 
197
195
  ```typescript
198
- // object-merge.ts
199
- import { Benchmark, printTableReports } from 'overtake';
196
+ // Compare V8 vs JSON serialization
197
+ const suite = benchmark('10K strings', () => Array.from({ length: 10_000 }, (_, i) => `string-${i}`));
200
198
 
201
- const benchmark = new Benchmark('1K objects', () => Array.from({ length: 1_000 }, (_, idx) => ({ [idx]: idx })));
202
-
203
- benchmark.target('spread operator').measure('merge', (_, input) => {
204
- return input.reduce((acc, obj) => ({ ...acc, ...obj }), {});
205
- });
199
+ suite
200
+ .target('V8', async () => {
201
+ const { serialize } = await import('node:v8');
202
+ return { serialize };
203
+ })
204
+ .measure('serialize', ({ serialize }, input) => serialize(input));
206
205
 
207
- benchmark.target('Object.assign in reduce').measure('merge', (_, input) => {
208
- return input.reduce((acc, obj) => {
209
- Object.assign(acc, obj);
210
- return acc;
211
- }, {});
212
- });
206
+ suite.target('JSON').measure('serialize', (_, input) => JSON.stringify(input));
207
+ ```
213
208
 
214
- benchmark.target('Object.assign spread').measure('merge', (_, input) => {
215
- return Object.assign({}, ...input);
216
- });
209
+ **[📁 More examples in `/examples`](./examples/)**
217
210
 
218
- const reports = await benchmark.execute({
219
- reportTypes: ['ops', 'mean'],
220
- maxCycles: 10_000,
221
- });
211
+ ## CLI Options
222
212
 
223
- printTableReports(reports);
213
+ ```bash
214
+ npx overtake <pattern> [options]
224
215
  ```
225
216
 
226
- **Key insights:**
217
+ | Option | Short | Description | Default |
218
+ | ---------------- | ----- | ---------------------------------------- | --------- |
219
+ | `--format` | `-f` | Output format: `simple`, `table`, `json` | `simple` |
220
+ | `--report-types` | `-r` | Stats to show: `ops`, `mean`, `p95`, etc | `['ops']` |
221
+ | `--workers` | `-w` | Concurrent workers | CPU count |
222
+ | `--min-cycles` | | Minimum iterations | 50 |
223
+ | `--max-cycles` | | Maximum iterations | 1000 |
227
224
 
228
- - Spread operator in reduce is ~50% slower due to object recreation
229
- - `Object.assign` with spread is most concise and performant
230
- - Mutating approaches (assign in reduce) offer similar performance
225
+ ### Example Commands
231
226
 
232
- ## CLI Usage
227
+ ```bash
228
+ # Run all benchmarks with table output
229
+ npx overtake "**/*.bench.ts" -f table
233
230
 
234
- ### Basic Command
231
+ # Show detailed statistics
232
+ npx overtake bench.ts -r ops mean p95 p99
235
233
 
236
- ```bash
237
- npx overtake <pattern> [options]
234
+ # Output JSON for CI
235
+ npx overtake bench.ts -f json > results.json
238
236
  ```
239
237
 
240
- ### Options
238
+ ## Troubleshooting
241
239
 
242
- | Option | Description | Default |
243
- | -------------------- | ------------------------------------------------------------------------------------------------------- | --------- |
244
- | `-f, --format` | Output format: `simple`, `table`, `json`, `pjson` | `simple` |
245
- | `-r, --report-types` | Statistics to display: `ops`, `mean`, `median`, `mode`, `min`, `max`, `p50`, `p75`, `p90`, `p95`, `p99` | `['ops']` |
246
- | `-w, --workers` | Number of concurrent worker threads | CPU count |
247
- | `--warmup-cycles` | Number of warmup iterations before measurement | 20 |
248
- | `--min-cycles` | Minimum measurement cycles | 50 |
249
- | `--max-cycles` | Maximum measurement cycles | 1000 |
250
- | `--abs-threshold` | Absolute error threshold in nanoseconds | 1000 |
251
- | `--rel-threshold` | Relative error threshold (0-1) | 0.02 |
240
+ ### "Cannot find module" in worker
252
241
 
253
- ### Examples
242
+ **Solution**: Use dynamic imports inside target callbacks (see [Critical section](#️-critical-dynamic-imports-required))
254
243
 
255
- ```bash
256
- # Run all benchmarks in a directory
257
- npx overtake "src/**/*.bench.ts" -f table
244
+ ### No output from benchmark
258
245
 
259
- # Show detailed statistics
260
- npx overtake benchmark.ts -r ops mean median p95 p99
246
+ **Solution**: In CLI mode, don't import Benchmark or call `.execute()`. Use the global `benchmark` function.
261
247
 
262
- # Increase precision with more cycles
263
- npx overtake benchmark.ts --min-cycles 100 --max-cycles 10000
248
+ ### Results vary between runs
264
249
 
265
- # Output JSON for CI/CD integration
266
- npx overtake benchmark.ts -f json > results.json
267
- ```
250
+ **Solution**: Increase `--min-cycles` for more samples, or use the `gcBlock` pattern to prevent garbage collection.
251
+
252
+ **[🐛 Report issues](https://github.com/3axap4eHko/overtake/issues)**
268
253
 
269
254
  ## License
270
255
 
271
- License [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0)
272
- Copyright (c) 2021-present Ivan Zakharchanka
256
+ [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) © 2021-2025 Ivan Zakharchanka
273
257
 
274
258
  [npm-url]: https://www.npmjs.com/package/overtake
275
259
  [downloads-image]: https://img.shields.io/npm/dw/overtake.svg?maxAge=43200
276
260
  [npm-image]: https://img.shields.io/npm/v/overtake.svg?maxAge=43200
277
261
  [github-url]: https://github.com/3axap4eHko/overtake/actions/workflows/cicd.yml
278
262
  [github-image]: https://github.com/3axap4eHko/overtake/actions/workflows/cicd.yml/badge.svg
279
- [codecov-url]: https://codecov.io/gh/3axap4eHko/overtake
280
- [codecov-image]: https://codecov.io/gh/3axap4eHko/overtake/branch/master/graph/badge.svg?token=JZ8QCGH6PI
281
- [codeclimate-url]: https://codeclimate.com/github/3axap4eHko/overtake/maintainability
282
- [codeclimate-image]: https://api.codeclimate.com/v1/badges/0ba20f27f6db2b0fec8c/maintainability
283
- [snyk-url]: https://snyk.io/test/npm/overtake/latest
284
- [snyk-image]: https://img.shields.io/snyk/vulnerabilities/github/3axap4eHko/overtake.svg?maxAge=43200
package/build/cli.cjs CHANGED
@@ -55,21 +55,18 @@ const require1 = (0, _nodemodule.createRequire)(require("url").pathToFileURL(__f
55
55
  const { name, description, version } = require1('../package.json');
56
56
  const commander = new _commander.Command();
57
57
  const transpile = async (code)=>{
58
- const ast = await (0, _core.parse)(code, {
59
- syntax: 'typescript',
60
- dynamicImport: true,
61
- target: 'esnext'
62
- });
63
- const output = await (0, _core.print)(ast, {
64
- module: {
65
- type: 'es6'
66
- },
58
+ const output = await (0, _core.transform)(code, {
59
+ filename: 'benchmark.ts',
67
60
  jsc: {
68
- target: 'esnext',
69
61
  parser: {
70
- syntax: 'typescript'
62
+ syntax: 'typescript',
63
+ tsx: false,
64
+ dynamicImport: true
71
65
  },
72
- experimental: {}
66
+ target: 'esnext'
67
+ },
68
+ module: {
69
+ type: 'es6'
73
70
  }
74
71
  });
75
72
  return output.code;
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 { parse, print } 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 ast = await parse(code, {\n syntax: 'typescript',\n dynamicImport: true,\n target: 'esnext',\n });\n\n const output = await print(ast, {\n module: {\n type: 'es6',\n },\n jsc: {\n target: 'esnext',\n parser: {\n syntax: 'typescript',\n },\n experimental: {},\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({ benchmark }),\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","ast","parse","syntax","dynamicImport","target","output","print","module","type","jsc","parser","experimental","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","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","argv"],"mappings":"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"}
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({ benchmark }),\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","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
@@ -1,7 +1,7 @@
1
1
  import { createRequire, Module } from 'node:module';
2
2
  import { SyntheticModule, createContext, SourceTextModule } from 'node:vm';
3
3
  import { stat, readFile } from 'node:fs/promises';
4
- import { parse, print } from '@swc/core';
4
+ import { transform } from '@swc/core';
5
5
  import { Command, Option } from 'commander';
6
6
  import { glob } from 'glob';
7
7
  import { Benchmark, printTableReports, printJSONReports, printSimpleReports, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from "./index.js";
@@ -10,21 +10,18 @@ const require = createRequire(import.meta.url);
10
10
  const { name, description, version } = require('../package.json');
11
11
  const commander = new Command();
12
12
  const transpile = async (code)=>{
13
- const ast = await parse(code, {
14
- syntax: 'typescript',
15
- dynamicImport: true,
16
- target: 'esnext'
17
- });
18
- const output = await print(ast, {
19
- module: {
20
- type: 'es6'
21
- },
13
+ const output = await transform(code, {
14
+ filename: 'benchmark.ts',
22
15
  jsc: {
23
- target: 'esnext',
24
16
  parser: {
25
- syntax: 'typescript'
17
+ syntax: 'typescript',
18
+ tsx: false,
19
+ dynamicImport: true
26
20
  },
27
- experimental: {}
21
+ target: 'esnext'
22
+ },
23
+ module: {
24
+ type: 'es6'
28
25
  }
29
26
  });
30
27
  return output.code;
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 { parse, print } 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 ast = await parse(code, {\n syntax: 'typescript',\n dynamicImport: true,\n target: 'esnext',\n });\n\n const output = await print(ast, {\n module: {\n type: 'es6',\n },\n jsc: {\n target: 'esnext',\n parser: {\n syntax: 'typescript',\n },\n experimental: {},\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({ benchmark }),\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","parse","print","Command","Option","glob","Benchmark","printTableReports","printJSONReports","printSimpleReports","DEFAULT_REPORT_TYPES","DEFAULT_WORKERS","REPORT_TYPES","require","url","name","description","version","commander","transpile","code","ast","syntax","dynamicImport","target","output","module","type","jsc","parser","experimental","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","imports","Map","link","specifier","referencingModule","has","get","mod","isBuiltin","resolve","exportNames","Object","keys","imported","forEach","key","setExport","identifier","set","evaluate","reports","execute","format","argv"],"mappings":"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"}
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({ benchmark }),\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","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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"}
@@ -1,8 +1,8 @@
1
- const suite = benchmark('1M array of strings', () => Array.from({ length: 1_000_000 }, (_, idx) => `${idx}`))
1
+ const copySuite = benchmark('1M array of strings', () => Array.from({ length: 1_000_000 }, (_, idx) => `${idx}`))
2
2
  .feed('1M array of numbers', () => Array.from({ length: 1_000_000 }, (_, idx) => idx))
3
3
  .feed('1M typed array', () => new Uint32Array(1_000_000).map((_, idx) => idx));
4
4
 
5
- suite.target('for loop').measure('copy half', (_, input) => {
5
+ copySuite.target('for loop').measure('copy half', (_, input) => {
6
6
  const n = input?.length ?? 0;
7
7
  const mid = n / 2;
8
8
  for (let i = 0; i < mid; i++) {
@@ -10,7 +10,7 @@ suite.target('for loop').measure('copy half', (_, input) => {
10
10
  }
11
11
  });
12
12
 
13
- suite.target('copyWithin').measure('copy half', (_, input) => {
13
+ copySuite.target('copyWithin').measure('copy half', (_, input) => {
14
14
  const n = input?.length ?? 0;
15
15
  const mid = n / 2;
16
16
  input.copyWithin(mid, 0, mid);
@@ -0,0 +1,95 @@
1
+ import { Benchmark, DEFAULT_REPORT_TYPES, printJSONReports } from '../build/index.js';
2
+ import { randomUUID, randomInt } from 'node:crypto';
3
+
4
+ const length = 10 ** 4;
5
+ // creates a benchmark suite with 3 data feeds of objects, strings and numbers
6
+ const suite = new Benchmark('1M array of objects', () =>
7
+ Array.from({ length }, (_, idx) => ({
8
+ string: randomUUID(),
9
+ number: randomInt(length),
10
+ boolean: idx % 3 === 0,
11
+ })),
12
+ )
13
+ .feed('1M array of strings', () => Array.from({ length }, () => randomUUID()))
14
+ .feed('1M array of numbers', () => Array.from({ length }, () => randomInt(length)));
15
+
16
+ // create a specific benchmark target this way v8Target type is aware of
17
+ // serialize, deserialize and serialized properties inside the context
18
+ const v8Target = suite.target('v8', async () => {
19
+ const { serialize, deserialize } = await import('node:v8');
20
+ const serialized: Buffer = Buffer.from([]);
21
+ return { serialize, deserialize, serialized };
22
+ });
23
+
24
+ v8Target
25
+ .measure('serialize', ({ serialize }, input) => {
26
+ serialize(input);
27
+ })
28
+ .pre(async (_ctx, _input) => {
29
+ // executed before measurement
30
+ })
31
+ .post(async (_ctx, _input) => {
32
+ // executed before measurement
33
+ });
34
+
35
+ v8Target
36
+ .measure('deserialize', ({ deserialize, serialized }) => {
37
+ deserialize(serialized);
38
+ })
39
+ .pre(async (ctx, input) => {
40
+ // since there is no serialized data pre hook prepares it
41
+ // it serializes before each measurement
42
+ ctx.serialized = ctx.serialize(input);
43
+ })
44
+ .post(async (ctx) => {
45
+ // clean it up to avoid GC trigger during measurement
46
+ ctx.serialized = undefined as unknown as Buffer;
47
+ });
48
+
49
+ v8Target.teardown(async () => {
50
+ // teardown the benchmark if needed free up resources, clean and etc
51
+ });
52
+
53
+ const jsonTarget = suite.target('json', () => {
54
+ const { parse, stringify } = JSON;
55
+ const serialized: string = '';
56
+ return { parse, stringify, serialized };
57
+ });
58
+
59
+ jsonTarget
60
+ .measure('stringify', ({ stringify }, input) => {
61
+ stringify(input);
62
+ })
63
+ .pre(async (_ctx, _input) => {
64
+ // executed before measurement
65
+ })
66
+ .post(async (_ctx, _input) => {
67
+ // executed before measurement
68
+ });
69
+
70
+ jsonTarget
71
+ .measure('parsse', ({ parse, serialized }) => {
72
+ parse(serialized);
73
+ })
74
+ .pre(async (ctx, input) => {
75
+ ctx.serialized = ctx.stringify(input);
76
+ })
77
+ .post(async (ctx) => {
78
+ ctx.serialized = '';
79
+ });
80
+
81
+ jsonTarget.teardown(async () => {
82
+ // teardown the benchmark if needed free up resources, clean and etc
83
+ });
84
+
85
+ const reports = await suite.execute({
86
+ workers: 10,
87
+ warmupCycles: 20,
88
+ maxCycles: 100,
89
+ minCycles: 100,
90
+ absThreshold: 1_000,
91
+ relThreshold: 0.02,
92
+ reportTypes: DEFAULT_REPORT_TYPES,
93
+ });
94
+
95
+ printJSONReports(reports, 2);
@@ -1,39 +1,39 @@
1
1
  import { Benchmark, printSimpleReports } from '../build/index.js';
2
2
 
3
- const benchmark = new Benchmark('1K array of objects', () => Array.from({ length: 1_000 }, (_, idx) => ({ [idx]: idx })));
3
+ const objectMergeSuite = new Benchmark('1K array of objects', () => Array.from({ length: 1_000 }, (_, idx) => ({ [idx]: idx })));
4
4
 
5
- benchmark.target('reduce destructure').measure('data', (_, input) => {
5
+ objectMergeSuite.target('reduce destructure').measure('data', (_, input) => {
6
6
  input.reduce((acc, obj) => {
7
7
  return { ...acc, ...obj };
8
8
  }, {});
9
9
  });
10
10
 
11
- benchmark.target('reduce assign').measure('data', (_, input) => {
11
+ objectMergeSuite.target('reduce assign').measure('data', (_, input) => {
12
12
  input.reduce((acc, obj) => {
13
13
  Object.assign(acc, obj);
14
14
  return acc;
15
15
  }, {});
16
16
  });
17
17
 
18
- benchmark.target('forEach assign').measure('data', (_, input) => {
18
+ objectMergeSuite.target('forEach assign').measure('data', (_, input) => {
19
19
  const result = {};
20
20
  input.forEach((obj) => {
21
21
  Object.assign(result, obj);
22
22
  });
23
23
  });
24
24
 
25
- benchmark.target('for assign').measure('data', (_, input) => {
25
+ objectMergeSuite.target('for assign').measure('data', (_, input) => {
26
26
  const result = {};
27
27
  for (let i = 0; i < input.length; i++) {
28
28
  Object.assign(result, input[i]);
29
29
  }
30
30
  });
31
31
 
32
- benchmark.target('assign').measure('data', (_, input) => {
32
+ objectMergeSuite.target('assign').measure('data', (_, input) => {
33
33
  Object.assign({}, ...input);
34
34
  });
35
35
 
36
- const reports = await benchmark.execute({
36
+ const reports = await objectMergeSuite.execute({
37
37
  reportTypes: ['ops'],
38
38
  maxCycles: 10_000,
39
39
  });
@@ -0,0 +1,16 @@
1
+ // run using the following command
2
+ // npx overtake examples/quick-start.ts
3
+
4
+ const sumSuite = benchmark('1M array', () => Array.from({ length: 1_000_000 }, (_, idx) => idx));
5
+
6
+ sumSuite.target('for loop').measure('sum', (_, input) => {
7
+ const n = input.length;
8
+ let sum = 0;
9
+ for (let i = 0; i < n; i++) {
10
+ sum += input[i];
11
+ }
12
+ });
13
+
14
+ sumSuite.target('reduce').measure('sum', (_, input) => {
15
+ input.reduce((a, b) => a + b, 0);
16
+ });
@@ -0,0 +1,22 @@
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
+ });
package/package.json CHANGED
@@ -1,10 +1,23 @@
1
1
  {
2
2
  "name": "overtake",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "NodeJS performance benchmark",
5
- "main": "build/index.js",
6
- "types": "build/index.d.ts",
7
5
  "type": "module",
6
+ "types": "build/index.d.ts",
7
+ "main": "build/index.cjs",
8
+ "module": "build/index.js",
9
+ "exports": {
10
+ "types": "./build/index.d.ts",
11
+ "require": "./build/index.cjs",
12
+ "import": "./build/index.js"
13
+ },
14
+ "typesVersions": {
15
+ "*": {
16
+ "*": [
17
+ "build/index.d.ts"
18
+ ]
19
+ }
20
+ },
8
21
  "bin": {
9
22
  "overtake": "bin/overtake.js"
10
23
  },
package/src/cli.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire, Module } from 'node:module';
2
2
  import { SyntheticModule, createContext, SourceTextModule } from 'node:vm';
3
3
  import { stat, readFile } from 'node:fs/promises';
4
- import { parse, print } from '@swc/core';
4
+ import { transform } from '@swc/core';
5
5
  import { Command, Option } from 'commander';
6
6
  import { glob } from 'glob';
7
7
  import { Benchmark, printTableReports, printJSONReports, printSimpleReports, DEFAULT_REPORT_TYPES, DEFAULT_WORKERS } from './index.js';
@@ -13,22 +13,18 @@ const { name, description, version } = require('../package.json');
13
13
  const commander = new Command();
14
14
 
15
15
  const transpile = async (code: string): Promise<string> => {
16
- const ast = await parse(code, {
17
- syntax: 'typescript',
18
- dynamicImport: true,
19
- target: 'esnext',
20
- });
21
-
22
- const output = await print(ast, {
23
- module: {
24
- type: 'es6',
25
- },
16
+ const output = await transform(code, {
17
+ filename: 'benchmark.ts',
26
18
  jsc: {
27
- target: 'esnext',
28
19
  parser: {
29
20
  syntax: 'typescript',
21
+ tsx: false,
22
+ dynamicImport: true,
30
23
  },
31
- experimental: {},
24
+ target: 'esnext',
25
+ },
26
+ module: {
27
+ type: 'es6',
32
28
  },
33
29
  });
34
30
  return output.code;