Skip to content

Commit 795ab1c

Browse files
committed
feat(core): introduce new "accurate" engine
1 parent 1cb6e1a commit 795ab1c

File tree

21 files changed

+1843
-36
lines changed

21 files changed

+1843
-36
lines changed

eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ export default defineConfig(
166166
'site',
167167
'astro.config.js',
168168
'.astro/**/*',
169+
'PROTOTYPE_CUSTOM_ENGINE.ts',
169170
],
170171
},
171172
);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* AccurateEngine Example
3+
*
4+
* This example demonstrates how to use the AccurateEngine for high-accuracy
5+
* benchmarking with V8 optimization guards.
6+
*
7+
* Run with: node --allow-natives-syntax examples/accurate-engine-example.js
8+
*/
9+
10+
import { tmpdir } from 'node:os';
11+
import { join } from 'node:path';
12+
13+
import { ModestBenchConfigurationManager } from '../src/config/manager.js';
14+
import { ModestBenchErrorManager } from '../src/core/error-manager.js';
15+
import { BenchmarkFileLoader } from '../src/core/loader.js';
16+
import { AccurateEngine } from '../src/index.js';
17+
import { ModestBenchProgressManager } from '../src/progress/manager.js';
18+
import { ModestBenchReporterRegistry } from '../src/reporters/registry.js';
19+
import { FileHistoryStorage } from '../src/storage/history.js';
20+
21+
const main = async () => {
22+
console.log('🎯 AccurateEngine Example\n');
23+
24+
// Create engine with all dependencies
25+
const engine = new AccurateEngine({
26+
configManager: new ModestBenchConfigurationManager(),
27+
errorManager: new ModestBenchErrorManager(),
28+
fileLoader: new BenchmarkFileLoader(),
29+
historyStorage: new FileHistoryStorage({
30+
storageDir: join(tmpdir(), '.modestbench-example'),
31+
}),
32+
progressManager: new ModestBenchProgressManager(),
33+
reporterRegistry: new ModestBenchReporterRegistry(),
34+
});
35+
36+
// Configuration for accurate measurement
37+
const config = {
38+
config: {
39+
iterations: 100, // Minimum iterations per task
40+
limitBy: 'any', // Stop at first limit reached
41+
quiet: false, // Show progress
42+
time: 1000, // Maximum time per task (1 second)
43+
verbose: true, // Detailed output
44+
warmup: 20, // 20 warmup iterations
45+
},
46+
// Point to example benchmark files
47+
pattern: 'examples/benchmarks/*.bench.js',
48+
reporters: ['human'],
49+
};
50+
51+
try {
52+
console.log('Running benchmarks with AccurateEngine...\n');
53+
54+
const result = await engine.execute(config);
55+
56+
console.log('\n✅ Benchmark complete!\n');
57+
console.log('Summary:');
58+
console.log(` Files: ${result.files.length}`);
59+
console.log(` Suites: ${result.summary.totalSuites}`);
60+
console.log(` Tasks: ${result.summary.totalTasks}`);
61+
console.log(` Total operations: ${result.summary.totalOperations}`);
62+
console.log(` Duration: ${(result.duration / 1000).toFixed(2)}s`);
63+
64+
if (result.summary.fastest) {
65+
console.log(`\n⚡ Fastest: ${result.summary.fastest.name}`);
66+
console.log(
67+
` ${result.summary.fastest.opsPerSecond.toFixed(2)} ops/sec`,
68+
);
69+
}
70+
71+
if (result.summary.slowest) {
72+
console.log(`\n🐌 Slowest: ${result.summary.slowest.name}`);
73+
console.log(
74+
` ${result.summary.slowest.opsPerSecond.toFixed(2)} ops/sec`,
75+
);
76+
}
77+
} catch (error) {
78+
console.error(
79+
'❌ Error running benchmarks:',
80+
error instanceof Error ? error.message : String(error),
81+
);
82+
process.exit(1);
83+
}
84+
};
85+
86+
void main();

site/src/content/docs/getting-started/index.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,45 @@ Run benchmarks with options:
122122
modestbench run --iterations 5000 --reporters human,json
123123
```
124124

125+
### Choosing an Engine
126+
127+
modestbench provides two engines with different trade-offs:
128+
129+
```bash
130+
# Default: tinybench (fast, good for development)
131+
modestbench run
132+
133+
# Accurate engine (higher precision, recommended for production benchmarks)
134+
node --allow-natives-syntax ./node_modules/.bin/modestbench run --engine accurate
135+
```
136+
137+
**Engine Comparison:**
138+
139+
| Feature | `tinybench` (default) | `accurate` |
140+
|---------|----------------------|------------|
141+
| Speed | ⚡ Very fast | 🐢 Slower (more thorough) |
142+
| Statistical Quality | ✅ Good | ⭐ Excellent |
143+
| Outlier Removal | ✅ IQR-based | ✅ IQR-based |
144+
| V8 Optimization Guards | ❌ No | ✅ Yes (prevents JIT interference) |
145+
| Requirements | None | `--allow-natives-syntax` flag |
146+
| Best For | Development, CI | Production benchmarks, publications |
147+
148+
:::tip[When to Use Accurate Engine]
149+
Use the `accurate` engine when:
150+
151+
- Publishing benchmark results
152+
- Making critical performance decisions
153+
- Comparing micro-optimizations
154+
- Needing the highest statistical quality
155+
156+
Use the default `tinybench` engine when:
157+
158+
- Rapid iteration during development
159+
- CI/CD performance regression tests
160+
- General performance comparisons
161+
162+
:::
163+
125164
Run specific files or directories:
126165

127166
```bash

site/src/content/docs/guides/advanced.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,107 @@ title: Advanced Usage
33
description: Advanced features and patterns for modestbench
44
---
55

6+
## Benchmark Engines
7+
8+
ModestBench provides two engines with different performance characteristics and statistical approaches.
9+
10+
### Engine Selection
11+
12+
Choose an engine based on your requirements:
13+
14+
```bash
15+
# Tinybench engine (default) - fast development iteration
16+
modestbench run --engine tinybench
17+
18+
# Accurate engine - high-precision measurements
19+
node --allow-natives-syntax ./node_modules/.bin/modestbench run --engine accurate
20+
```
21+
22+
### Statistical Improvements
23+
24+
Both engines now use **IQR (Interquartile Range) outlier removal** to filter extreme values caused by:
25+
26+
- Garbage collection pauses
27+
- System interruptions
28+
- Background processes
29+
- OS scheduler variations
30+
31+
This results in more stable and reliable measurements compared to raw statistical analysis.
32+
33+
#### AccurateEngine Statistical Features
34+
35+
The `accurate` engine provides enhanced statistical analysis:
36+
37+
1. **V8 Optimization Guards**: Uses V8 intrinsics (`%NeverOptimizeFunction`) to prevent JIT compiler interference with measurements
38+
2. **IQR Outlier Removal**: Automatically removes extreme outliers (beyond Q1 - 1.5×IQR and Q3 + 1.5×IQR)
39+
3. **Comprehensive Statistics**:
40+
- Mean, min, max execution times
41+
- Standard deviation and variance
42+
- **Coefficient of Variation (CV)**: Measures relative variability (`stdDev / mean × 100`)
43+
- 95th and 99th percentiles
44+
- Margin of error (95% confidence interval)
45+
46+
#### Coefficient of Variation (CV)
47+
48+
The CV metric helps assess benchmark quality:
49+
50+
```text
51+
CV < 5% → Excellent (very stable)
52+
CV 5-10% → Good (acceptable variance)
53+
CV 10-20% → Fair (consider more samples)
54+
CV > 20% → Poor (investigate noise sources)
55+
```
56+
57+
Example output showing CV:
58+
59+
```bash
60+
$ modestbench run --engine accurate --allow-natives-syntax --reporters json
61+
{
62+
"name": "Array.push()",
63+
"mean": 810050, // nanoseconds
64+
"stdDev": 19842,
65+
"cv": 2.45, // 2.45% - excellent stability
66+
"marginOfError": 0.024,
67+
"p95": 845200,
68+
"p99": 862100
69+
}
70+
```
71+
72+
### Performance Comparison
73+
74+
Real-world comparison using `examples/benchmarks`:
75+
76+
```bash
77+
# Tinybench (fast iteration)
78+
$ modestbench run --engine tinybench --reporters json
79+
# Typical run time: 3-5 seconds for 5 benchmark files
80+
81+
# Accurate (high precision)
82+
$ node --allow-natives-syntax ./node_modules/.bin/modestbench run --engine accurate --reporters json
83+
# Typical run time: 8-12 seconds for 5 benchmark files
84+
```
85+
86+
The `accurate` engine takes ~2-3x longer but provides:
87+
88+
- More consistent results between runs
89+
- Better outlier filtering with V8 guards
90+
- Higher confidence in micro-optimizations
91+
92+
### Choosing the Right Engine
93+
94+
| Use Case | Recommended Engine |
95+
|----------|-------------------|
96+
| Development iteration | `tinybench` |
97+
| CI/CD regression tests | `tinybench` |
98+
| Blog post/publication | `accurate` |
99+
| Library optimization | `accurate` |
100+
| Micro-benchmark comparison | `accurate` |
101+
| Algorithm selection | Either (results typically consistent) |
102+
103+
:::tip[Best Practice]
104+
Start with `tinybench` during development for fast feedback. Switch to `accurate` for final measurements and critical decisions.
105+
:::
106+
6107
## Multiple Suites
7108

8109
Organize related benchmarks into separate suites with independent setup and teardown:

site/src/content/docs/guides/cli.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,43 @@ modestbench run --warmup 100
9898

9999
Helps stabilize JIT compilation for more consistent results.
100100

101+
##### `--engine <name>`
102+
103+
Select the benchmark engine. Options: `tinybench` (default) or `accurate`.
104+
105+
```bash
106+
# Use the accurate engine for high-precision measurements
107+
node --allow-natives-syntax ./node_modules/.bin/modestbench run --engine accurate
108+
109+
# Use tinybench engine (default)
110+
modestbench run --engine tinybench
111+
```
112+
113+
**Engine Differences:**
114+
115+
- **`tinybench`** (default): Fast, lightweight engine suitable for development and CI. Uses IQR-based outlier removal.
116+
- **`accurate`**: High-precision engine with V8 optimization guards to prevent JIT compiler interference. Requires `--allow-natives-syntax` flag. Recommended for production benchmarks and critical performance measurements.
117+
118+
See the [Getting Started](/getting-started/#choosing-an-engine) guide for detailed comparison.
119+
120+
:::caution[Node.js Flag Required]
121+
The `accurate` engine requires running Node.js with the `--allow-natives-syntax` flag. This flag must be passed to the Node.js runtime, not to modestbench:
122+
123+
```bash
124+
# Using Node.js directly
125+
node --allow-natives-syntax ./node_modules/.bin/modestbench run --engine accurate
126+
127+
# Using npx (pass flag to Node.js)
128+
npx --node-arg=--allow-natives-syntax modestbench run --engine accurate
129+
130+
# Using package.json script
131+
# package.json: "bench": "node --allow-natives-syntax ./node_modules/.bin/modestbench run --engine accurate"
132+
npm run bench
133+
```
134+
135+
If the flag is not present, AccurateEngine will fall back to a less accurate mode and display a warning.
136+
:::
137+
101138
##### `--timeout <number>`
102139

103140
Maximum time in milliseconds for a single task before timing out.

site/src/content/docs/index.mdx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { Card, CardGrid } from '@astrojs/starlight/components';
2222

2323
<CardGrid stagger>
2424
<Card title="Fast & Accurate" icon="rocket">
25-
High-precision timing with statistical analysis powered by `tinybench`. Get reliable measurements you can trust.
25+
High-precision timing with advanced statistical analysis. Choose between the fast `tinybench` engine or the `accurate` engine with V8 optimization guards and IQR outlier removal.
2626
</Card>
2727

2828
<Card title="Multiple Output Formats" icon="document">
@@ -84,8 +84,10 @@ modestbench run --iterations 5000 --reporters human,json
8484

8585
## Why modestbench?
8686

87-
modestbench wraps [tinybench](https://github.com/tinylibs/tinybench) and enhances it with a bunch of features so you don't have to think:
87+
modestbench provides a powerful benchmarking framework with dual engines and rich features:
8888

89+
- **Dual engines** - Fast `tinybench` for development, `accurate` for precision measurements
90+
- **Advanced statistics** - IQR outlier removal, coefficient of variation, percentiles
8991
- **Project structure** - Organized benchmark files and suites
9092
- **Configuration management** - Multiple formats (JSON, YAML, JS, TS)
9193
- **Rich CLI** - Discover and run benchmarks with powerful options

src/cli/commands/run.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ interface RunOptions {
1919
bail?: boolean | undefined;
2020
config?: string | undefined;
2121
cwd: string;
22+
engine?: 'accurate' | 'tinybench' | undefined;
2223
exclude?: string[] | undefined;
2324
excludeTags?: string[] | undefined;
2425
iterations?: number | undefined;

src/cli/index.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
} from '../types/index.js';
2424

2525
import { bootstrap } from '../bootstrap.js';
26+
import { AccurateEngine, TinybenchEngine } from '../core/engines/index.js';
2627
import {
2728
CsvReporter,
2829
HumanReporter,
@@ -262,6 +263,14 @@ export const main = async (
262263
description: 'Exclude benchmarks with any of these tags',
263264
type: 'array',
264265
})
266+
.option('engine', {
267+
alias: 'e',
268+
choices: ['tinybench', 'accurate'] as const,
269+
default: 'tinybench' as const,
270+
description:
271+
'Benchmark engine: tinybench (default) or accurate (requires --allow-natives-syntax)',
272+
type: 'string',
273+
})
265274
.example([
266275
['$0 run', 'Run benchmarks in current directory and bench/'],
267276
['$0 run benchmarks/', 'Run all benchmarks in a directory'],
@@ -271,15 +280,21 @@ export const main = async (
271280
['$0 run benchmarks/ tests/perf/', 'Run multiple directories'],
272281
['$0 run --reporters json,csv', 'Use multiple reporters'],
273282
['$0 run --iterations 1000', 'Set iteration count'],
283+
['$0 run --engine accurate', 'Use high-accuracy engine'],
274284
['$0 run --bail', 'Stop on first failure'],
275285
]);
276286
},
277287
async (argv) => {
278-
const context = await createCliContext(argv, abortController!);
288+
const context = await createCliContext(
289+
argv,
290+
abortController!,
291+
argv.engine,
292+
);
279293
const exitCode = await runCommand(context, {
280294
bail: argv.bail,
281295
config: argv.config,
282296
cwd: argv.cwd,
297+
engine: argv.engine,
283298
exclude: argv.exclude,
284299
excludeTags: argv['exclude-tags'],
285300
iterations: argv.iterations,
@@ -503,9 +518,16 @@ export const main = async (
503518
const createCliContext = async (
504519
options: GlobalOptions,
505520
abortController: AbortController,
521+
engineType: 'accurate' | 'tinybench' = 'tinybench',
506522
): Promise<CliContext> => {
507523
try {
508-
const engine = bootstrap();
524+
const dependencies = bootstrap();
525+
526+
// Select engine based on type
527+
const engine =
528+
engineType === 'accurate'
529+
? new AccurateEngine(dependencies)
530+
: new TinybenchEngine(dependencies);
509531

510532
// Register built-in reporters
511533
engine.registerReporter(

0 commit comments

Comments
 (0)