overtake 1.0.0 โ†’ 1.0.1

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.
Files changed (2) hide show
  1. package/README.md +187 -50
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Overtake
2
2
 
3
- Performance benchmark for NodeJS
3
+ High-precision performance benchmarking library for Node.js with isolated worker thread execution and statistical convergence.
4
4
 
5
5
  [![Build Status][github-image]][github-url]
6
6
  [![NPM version][npm-image]][npm-url]
@@ -12,16 +12,42 @@ Performance benchmark for NodeJS
12
12
 
13
13
  ## Table of Contents
14
14
 
15
+ - [Why Overtake?](#why-overtake)
15
16
  - [Features](#features)
16
17
  - [Installing](#installing)
18
+ - [Quick Start](#quick-start)
19
+ - [API Guide](#api-guide)
17
20
  - [Examples](#examples)
21
+ - [CLI Usage](#cli-usage)
18
22
  - [License](#license)
19
23
 
24
+ ## Why Overtake?
25
+
26
+ Traditional JavaScript benchmarking tools often suffer from:
27
+
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
32
+
33
+ Overtake solves these problems by:
34
+
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
40
+
20
41
  ## Features
21
42
 
22
- - CLI
23
- - TypeScript support
24
- - Running in thread worker
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
25
51
 
26
52
  ## Installing
27
53
 
@@ -37,14 +63,108 @@ Using npm:
37
63
  $ npm install -D overtake
38
64
  ```
39
65
 
66
+ ## Quick Start
67
+
68
+ ### Basic Benchmark
69
+
70
+ Compare different implementations of the same operation:
71
+
72
+ ```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
+ });
87
+ ```
88
+
89
+ Run with CLI:
90
+
91
+ ```bash
92
+ npx overtake benchmark.ts -f table
93
+ ```
94
+
95
+ ## API Guide
96
+
97
+ ### Core Concepts
98
+
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
105
+
106
+ ```typescript
107
+ // Create a benchmark with optional initial feed
108
+ const suite = benchmark('Test name', () => generateInputData());
109
+
110
+ // Add more input variations
111
+ suite.feed('small dataset', () => generateSmallData()).feed('large dataset', () => generateLargeData());
112
+
113
+ // Define implementations to compare
114
+ suite.target('implementation A').measure('operation', (ctx, input) => {
115
+ // Your code here
116
+ });
117
+
118
+ suite.target('implementation B').measure('operation', (ctx, input) => {
119
+ // Alternative implementation
120
+ });
121
+ ```
122
+
123
+ ### Setup and Teardown
124
+
125
+ ```typescript
126
+ suite
127
+ .target('with setup', async () => {
128
+ // Setup: runs once before measurements
129
+ const connection = await createConnection();
130
+ return { connection };
131
+ })
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();
139
+ });
140
+ ```
141
+
142
+ ### Pre and Post Hooks
143
+
144
+ ```typescript
145
+ 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);
153
+ })
154
+ .post((ctx, input) => {
155
+ // Runs after EACH measurement
156
+ cleanupData(input);
157
+ });
158
+ ```
159
+
40
160
  ## Examples
41
161
 
42
- ### From command line
162
+ ### Example 1: Array Operations Comparison
43
163
 
44
- Create a benchmark file
164
+ This example compares different methods for copying array elements:
45
165
 
46
166
  ```typescript
47
- // src/__bench__/array-copy.ts
167
+ // array-copy.ts
48
168
  const suite = benchmark('1M array of strings', () => Array.from({ length: 1_000_000 }, (_, idx) => `${idx}`))
49
169
  .feed('1M array of numbers', () => Array.from({ length: 1_000_000 }, (_, idx) => idx))
50
170
  .feed('1M typed array', () => new Uint32Array(1_000_000).map((_, idx) => idx));
@@ -64,69 +184,86 @@ suite.target('copyWithin').measure('copy half', (_, input) => {
64
184
  });
65
185
  ```
66
186
 
67
- Run the command
68
-
69
- ```bash
70
- npx overtake src/__bench__/array-copy.ts -f table -r ops mode mean p99
71
- ```
187
+ **Key insights from results:**
72
188
 
73
- ```
74
- for loop copy half
75
- โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
76
- โ”‚ (index) โ”‚ ops โ”‚ mode โ”‚ mean โ”‚ p99 โ”‚ count โ”‚
77
- โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
78
- โ”‚ 1M typed array โ”‚ '3698 ops/s ยฑ 0.81%' โ”‚ '256.65 ยตs' โ”‚ '270.38 ยตs' โ”‚ '574.7 ยตs ยฑ 0.19%' โ”‚ '1000' โ”‚
79
- โ”‚ 1M array of numbers โ”‚ '2902 ops/s ยฑ 0.3%' โ”‚ '343.92 ยตs' โ”‚ '344.51 ยตs' โ”‚ '429.24 ยตs ยฑ 0.2%' โ”‚ '1000' โ”‚
80
- โ”‚ 1M array of strings โ”‚ '2277 ops/s ยฑ 0.46%' โ”‚ '397.15 ยตs' โ”‚ '438.99 ยตs' โ”‚ '569.15 ยตs ยฑ 0.12%' โ”‚ '1000' โ”‚
81
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
82
-
83
- copyWithin copy half
84
- โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
85
- โ”‚ (index) โ”‚ ops โ”‚ mode โ”‚ mean โ”‚ p99 โ”‚ count โ”‚
86
- โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
87
- โ”‚ 1M typed array โ”‚ '17454 ops/s ยฑ 0.67%' โ”‚ '53.11 ยตs' โ”‚ '57.29 ยตs' โ”‚ '81.18 ยตs ยฑ 1.54%' โ”‚ '1000' โ”‚
88
- โ”‚ 1M array of numbers โ”‚ '103 ops/s ยฑ 0.02%' โ”‚ '9.49 ms' โ”‚ '9.64 ms' โ”‚ '9.91 ms ยฑ 0.38%' โ”‚ '50' โ”‚
89
- โ”‚ 1M array of strings โ”‚ '101 ops/s ยฑ 0.06%' โ”‚ '9.55 ms' โ”‚ '9.87 ms' โ”‚ '10.87 ms ยฑ 2.67%' โ”‚ '98' โ”‚
90
- โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
91
- ```
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
92
192
 
93
- ### From a standalone module
193
+ ### Example 2: Object Merging Strategies
94
194
 
95
- Create a benchmark file
195
+ Compare different approaches to merge arrays of objects:
96
196
 
97
197
  ```typescript
98
- // src/__bench__/array-copy.js
198
+ // object-merge.ts
99
199
  import { Benchmark, printTableReports } from 'overtake';
100
200
 
101
- const benchmark = Benchmark.create('1M array of strings', () => Array.from({ length: 1_000_000 }, (_, idx) => `${idx}`))
102
- .feed('1M array of numbers', () => Array.from({ length: 1_000_000 }, (_, idx) => idx))
103
- .feed('1M typed array', () => new Uint32Array(1_000_000).map((_, idx) => idx));
201
+ const benchmark = new Benchmark('1K objects', () => Array.from({ length: 1_000 }, (_, idx) => ({ [idx]: idx })));
104
202
 
105
- benchmark.target('for loop').measure('copy half', (_, input) => {
106
- const n = input?.length ?? 0;
107
- const mid = n / 2;
108
- for (let i = 0; i < mid; i++) {
109
- input[i + mid] = input[i];
110
- }
203
+ benchmark.target('spread operator').measure('merge', (_, input) => {
204
+ return input.reduce((acc, obj) => ({ ...acc, ...obj }), {});
111
205
  });
112
206
 
113
- benchmark.target('copyWithin').measure('copy half', (_, input) => {
114
- const n = input?.length ?? 0;
115
- const mid = n / 2;
116
- input.copyWithin(mid, 0, mid);
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
+ });
213
+
214
+ benchmark.target('Object.assign spread').measure('merge', (_, input) => {
215
+ return Object.assign({}, ...input);
117
216
  });
118
217
 
119
218
  const reports = await benchmark.execute({
120
- reportTypes: ['ops', 'mode', 'mean', 'p99'],
219
+ reportTypes: ['ops', 'mean'],
220
+ maxCycles: 10_000,
121
221
  });
122
222
 
123
223
  printTableReports(reports);
124
224
  ```
125
225
 
126
- And run the command
226
+ **Key insights:**
227
+
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
231
+
232
+ ## CLI Usage
233
+
234
+ ### Basic Command
235
+
236
+ ```bash
237
+ npx overtake <pattern> [options]
238
+ ```
239
+
240
+ ### Options
241
+
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 |
252
+
253
+ ### Examples
127
254
 
128
255
  ```bash
129
- node src/__bench__/array-copy.js
256
+ # Run all benchmarks in a directory
257
+ npx overtake "src/**/*.bench.ts" -f table
258
+
259
+ # Show detailed statistics
260
+ npx overtake benchmark.ts -r ops mean median p95 p99
261
+
262
+ # Increase precision with more cycles
263
+ npx overtake benchmark.ts --min-cycles 100 --max-cycles 10000
264
+
265
+ # Output JSON for CI/CD integration
266
+ npx overtake benchmark.ts -f json > results.json
130
267
  ```
131
268
 
132
269
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overtake",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "NodeJS performance benchmark",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",