|
| 1 | +# Benchmarks |
| 2 | + |
| 3 | +Performance benchmarks for `tapable`, tracked over time via |
| 4 | +[CodSpeed](https://codspeed.io/). |
| 5 | + |
| 6 | +Runner stack: [tinybench](https://github.com/tinylibs/tinybench) + |
| 7 | +[`@codspeed/core`](https://www.npmjs.com/package/@codspeed/core) with a local |
| 8 | +`withCodSpeed()` wrapper ported from webpack's |
| 9 | +`test/BenchmarkTestCases.benchmark.mjs` (via enhanced-resolve). Locally it |
| 10 | +falls back to plain tinybench wall-clock measurements, and under |
| 11 | +`CodSpeedHQ/action` in CI it automatically switches to CodSpeed's |
| 12 | +instrumentation mode. |
| 13 | + |
| 14 | +## Running locally |
| 15 | + |
| 16 | +```sh |
| 17 | +npm run benchmark |
| 18 | +``` |
| 19 | + |
| 20 | +Optional substring filter to run only matching cases: |
| 21 | + |
| 22 | +```sh |
| 23 | +npm run benchmark -- sync |
| 24 | +BENCH_FILTER=async-parallel npm run benchmark |
| 25 | +``` |
| 26 | + |
| 27 | +Locally the runner uses tinybench's wall-clock measurements and prints a |
| 28 | +table of ops/s, mean, p99, and relative margin of error per task. Under CI, |
| 29 | +the bridge detects the CodSpeed runner environment and switches to |
| 30 | +instruction-counting mode automatically. |
| 31 | + |
| 32 | +The V8 flags in `package.json` (`--no-opt --predictable --hash-seed=1` etc.) |
| 33 | +are required by CodSpeed's instrumentation mode for deterministic results — |
| 34 | +do not drop them. |
| 35 | + |
| 36 | +### Optional: running real instruction counts locally |
| 37 | + |
| 38 | +If you want to reproduce CI's exact instrument-count numbers on your own |
| 39 | +machine (Linux only — the underlying Valgrind tooling has no macOS backend), |
| 40 | +install the standalone CodSpeed CLI and wrap `npm run benchmark` with it: |
| 41 | + |
| 42 | +```sh |
| 43 | +curl -fsSL https://codspeed.io/install.sh | bash |
| 44 | +codspeed run npm run benchmark |
| 45 | +``` |
| 46 | + |
| 47 | +This is only useful if you want to debug an instruction-count regression |
| 48 | +outside CI. Day-to-day benchmark iteration should use `npm run benchmark` |
| 49 | +directly (wall-clock mode). |
| 50 | + |
| 51 | +## Layout |
| 52 | + |
| 53 | +``` |
| 54 | +benchmark/ |
| 55 | +├── run.mjs # entry point: discovers cases, runs bench |
| 56 | +├── with-codspeed.mjs # local @codspeed/core <-> tinybench bridge |
| 57 | +└── cases/ |
| 58 | + └── <case-name>/ |
| 59 | + └── index.bench.mjs # default export: register(bench, ctx) |
| 60 | +``` |
| 61 | + |
| 62 | +Each case directory must contain `index.bench.mjs` exporting a default |
| 63 | +function with the signature: |
| 64 | + |
| 65 | +```js |
| 66 | +export default function register(bench, { caseName, caseDir }) { |
| 67 | + bench.add("my case: descriptive name", () => { |
| 68 | + // ... hook calls ... |
| 69 | + }); |
| 70 | +} |
| 71 | +``` |
| 72 | + |
| 73 | +## Existing cases |
| 74 | + |
| 75 | +| Case | What it measures | |
| 76 | +| ----------------------------- | ------------------------------------------------------------------------------------- | |
| 77 | +| `sync-hook` | Steady-state `SyncHook#call` at tap counts 0/1/5/10/20/50 and arg counts 0..5 | |
| 78 | +| `sync-bail-hook` | `SyncBailHook#call`, full walk vs. bail at start / middle / end | |
| 79 | +| `sync-waterfall-hook` | Value-threading through taps that all return / all skip / mixed | |
| 80 | +| `sync-loop-hook` | Single-pass and multi-pass loops | |
| 81 | +| `async-series-hook` | `callAsync` and `promise`, sync / async / promise tap flavors | |
| 82 | +| `async-series-bail-hook` | Full walk vs. bail, for sync and callback-async taps | |
| 83 | +| `async-series-waterfall-hook` | Waterfall for sync / async / promise taps | |
| 84 | +| `async-series-loop-hook` | Single-pass and multi-pass async loops | |
| 85 | +| `async-parallel-hook` | Fan-out across sync / async / promise taps | |
| 86 | +| `async-parallel-bail-hook` | Parallel race with and without a bailing tap | |
| 87 | +| `hook-map` | `HookMap#for` hot / cold / missing lookups plus interceptor factories | |
| 88 | +| `multi-hook` | Fan-out registration and `isUsed` / `intercept` across a 3-hook `MultiHook` | |
| 89 | +| `interceptors-sync` | Baseline vs. `call`, `tap`, combined, multiple, and register interceptors on SyncHook | |
| 90 | +| `interceptors-async` | Same matrix on `AsyncSeriesHook` and `AsyncParallelHook` | |
| 91 | +| `tap-registration` | `tap` / `tapAsync` / `tapPromise` with string, object, stage, and `before` options | |
| 92 | +| `hook-compile` | First-call code-gen cost for every hook type (5 taps + first call per iteration) | |
| 93 | + |
| 94 | +Add new cases by creating a new directory under `cases/` — `run.mjs` will |
| 95 | +pick it up automatically on the next run. |
0 commit comments