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.
- package/README.md +187 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Overtake
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
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
|
-
###
|
|
162
|
+
### Example 1: Array Operations Comparison
|
|
43
163
|
|
|
44
|
-
|
|
164
|
+
This example compares different methods for copying array elements:
|
|
45
165
|
|
|
46
166
|
```typescript
|
|
47
|
-
//
|
|
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
|
-
|
|
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
|
|
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
|
-
###
|
|
193
|
+
### Example 2: Object Merging Strategies
|
|
94
194
|
|
|
95
|
-
|
|
195
|
+
Compare different approaches to merge arrays of objects:
|
|
96
196
|
|
|
97
197
|
```typescript
|
|
98
|
-
//
|
|
198
|
+
// object-merge.ts
|
|
99
199
|
import { Benchmark, printTableReports } from 'overtake';
|
|
100
200
|
|
|
101
|
-
const benchmark = Benchmark
|
|
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('
|
|
106
|
-
|
|
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('
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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', '
|
|
219
|
+
reportTypes: ['ops', 'mean'],
|
|
220
|
+
maxCycles: 10_000,
|
|
121
221
|
});
|
|
122
222
|
|
|
123
223
|
printTableReports(reports);
|
|
124
224
|
```
|
|
125
225
|
|
|
126
|
-
|
|
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
|
-
|
|
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
|