Add necessary flags to dump optimized inlining info and fix marker placement in tinybench#83
Conversation
Add --perf-prof, --log-code, --no-log-source-code, --no-logfile-per-isolate and --logfile to getV8Flags in walltime mode. --perf-prof writes the jitdump samply uses to symbolicate JIT'd JS; --log-code writes the code log whose inlining map lets the symbolicating recover TurboFan/Maglev inlined frames the jitdump alone collapses. Source-code logging is left off to keep the log small. Refs COD-2821, COD-2822
0b43ff4 to
e07237e
Compare
Merging this PR will not alter performance
|
| Mode | Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|---|
| ❌ | Memory | test sync baz 100 |
1 B | 21 B | -95.24% |
| ❌ | Memory | test sync baz 100 |
1 B | 21 B | -95.24% |
| ❌ | Simulation | switch 1 |
101.2 µs | 375.1 µs | -73.01% |
| ❌ | Simulation | one |
402.7 µs | 590.6 µs | -31.81% |
| ❌ | Memory | wait 1ms |
7 B | 10 B | -30% |
| ❌ | Simulation | switch 1 |
131.5 µs | 187.3 µs | -29.77% |
| ❌ | Simulation | switch 1 |
130 µs | 183.4 µs | -29.09% |
| ❌ | Simulation | switch 1 |
129.8 µs | 182.7 µs | -28.96% |
| ❌ | Simulation | test_recursive_fibo_10 |
145.2 µs | 197.9 µs | -26.64% |
| ❌ | Simulation | test_recursive_fibo_10 |
146.6 µs | 197.7 µs | -25.85% |
| ❌ | Simulation | switch 2 |
11.2 µs | 12.8 µs | -12.49% |
| ❌ | Simulation | test sync baz 100 |
30.7 µs | 34.5 µs | -10.96% |
| ❌ | Simulation | test sync baz 100 |
30.6 µs | 34.3 µs | -10.67% |
| ⚡ | Memory | short body 3 |
25,960 B | 11 B | ×2,400 |
| ⚡ | Memory | one |
45.9 KB | 14.3 KB | ×3.2 |
| ⚡ | WallTime | switch 2 |
336 ns | 300 ns | +12% |
| ⚡ | WallTime | short body |
1.6 µs | 1.5 µs | +10.66% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing cod-2821-v8-maglevturbofan-inlining-loses-nested-inline-information (0b43ff4) with main (bcd7e64)
Footnotes
-
1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports. ↩
Greptile SummaryThis PR wires up the walltime measurement mode more precisely: it adds
Confidence Score: 3/5The core V8-flags and linuxPerf changes are safe; the new hook-based instrument-window placement in walltime.ts has two correctness holes that could crash all walltime runs or silently corrupt measurements. The installInstrumentHooks path has no integration-test coverage for walltime mode. If bench.opts.setup/teardown are undefined (the TypeScript type allows it and the no-op guarantee relies on undocumented tinybench internals), every walltime run throws TypeError. Separately, an async user setup hook causes the instrument window to open while setup is still in flight, inflating measurements. packages/tinybench-plugin/src/walltime.ts — installInstrumentHooks needs null-safe fallbacks for setup/teardown and needs to await the user setup hook before opening the instrument window. Important Files Changed
|
| const opts = this.bench.opts as { setup: Hook; teardown: Hook }; | ||
| const userSetup = opts.setup; | ||
| const userTeardown = opts.teardown; |
There was a problem hiding this comment.
If a user doesn't pass
setup/teardown to their Bench constructor, bench.opts.setup and bench.opts.teardown are typed as optional (setup?: Hook in BenchOptions). The comment asserts tinybench always resolves them to no-ops, but the explicit type cast as { setup: Hook; teardown: Hook } is a sign the compiler can't confirm this. If either value is undefined at the time installInstrumentHooks runs, calling userSetup(task, mode) or userTeardown(task, mode) will throw TypeError: X is not a function for every walltime benchmark run. Since the peerDependency spans all tinybench >=4.0.1 versions, this behaviour should not rely on an undocumented implementation detail. Nullish fallbacks cost nothing and eliminate the risk entirely.
| const opts = this.bench.opts as { setup: Hook; teardown: Hook }; | |
| const userSetup = opts.setup; | |
| const userTeardown = opts.teardown; | |
| const opts = this.bench.opts as { setup?: Hook; teardown?: Hook }; | |
| const noop: Hook = () => {}; | |
| const userSetup = opts.setup ?? noop; | |
| const userTeardown = opts.teardown ?? noop; |
| opts.setup = (task, mode) => { | ||
| const setupResult = userSetup(task, mode); | ||
| if (mode === "run") { | ||
| this.runStart = this.openInstrumentWindow(); | ||
| } | ||
| return setupResult; | ||
| }; |
There was a problem hiding this comment.
If the user supplied an async
setup hook, userSetup(task, mode) returns a Promise<void> that is captured in setupResult but not awaited before openInstrumentWindow() is called. The instrument window therefore opens while the async setup is still in flight, potentially attributing asynchronous prep time (e.g. data seeding, connection reset) to the measurement. Awaiting the user hook first keeps the marker placement accurate.
| opts.setup = (task, mode) => { | |
| const setupResult = userSetup(task, mode); | |
| if (mode === "run") { | |
| this.runStart = this.openInstrumentWindow(); | |
| } | |
| return setupResult; | |
| }; | |
| opts.setup = async (task, mode) => { | |
| await userSetup(task, mode); | |
| if (mode === "run") { | |
| this.runStart = this.openInstrumentWindow(); | |
| } | |
| }; |
| if (v8LogDir) { | ||
| flags.push( | ||
| ...[ | ||
| "--log-code", | ||
| "--no-log-source-code", | ||
| "--no-logfile-per-isolate", | ||
| `--logfile=${path.join(v8LogDir, V8_LOG_FILENAME_PATTERN)}`, | ||
| ], | ||
| ); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The
walltime case is missing a break statement, unlike the analysis case above it. While harmless here since there is no subsequent case, the inconsistency can confuse future readers and will trip no-fallthrough linters.
| if (v8LogDir) { | |
| flags.push( | |
| ...[ | |
| "--log-code", | |
| "--no-log-source-code", | |
| "--no-logfile-per-isolate", | |
| `--logfile=${path.join(v8LogDir, V8_LOG_FILENAME_PATTERN)}`, | |
| ], | |
| ); | |
| } | |
| } | |
| } | |
| if (v8LogDir) { | |
| flags.push( | |
| ...[ | |
| "--log-code", | |
| "--no-log-source-code", | |
| "--no-logfile-per-isolate", | |
| `--logfile=${path.join(v8LogDir, V8_LOG_FILENAME_PATTERN)}`, | |
| ], | |
| ); | |
| } | |
| break; | |
| } | |
| } |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
No description provided.