BenchmarkPrototypeMethodAccess constructs an engine and compiles its source inside the timed loop, so what it measures is dominated by startup rather than by the prototype access it is named for.
b.ResetTimer()
for i := 0; i < b.N; i++ {
p := driver.NewPaserati() // build engine, builtins, module loader
_, errs := p.RunString(tc.code) // lex, parse, check, compile, then run
}
The workload for one of the three cases is:
let str = "hello";
str.length;
The evidence that startup dominates
From a 16-commit same-host session on a dedicated c7a.2xlarge, absolute ns/op at the final commit:
1,021,813 … 1,108,974 ns all ten compile-in-loop cases
4,597,556 ns MatrixMult
126,747,628 ns Arith
712,648,894 ns FibPlaceholderRun
All ten land within 8% of each other at ~1 ms — across three different workloads (string length, array length, prototype chain) and across the three cache configurations Baseline, WithPrototypeCache and WithDetailedStats, whose difference is the entire point of the benchmark. If the prototype cache changed anything measurable here, Baseline and WithPrototypeCache would separate. They do not, because both are ~1 ms of NewPaserati() plus compiling three tokens.
A b.N sweep on the same host is consistent with that reading: this group moves a median of −37.8% from b.N=1 to b.N=256, which is the fixed first-iteration cost being amortised over more iterations. The five bench_test.go benchmarks, which compile above b.ResetTimer(), are flat over the same sweep.
Why it matters
bench-ratchet's default scope is pkg/vm and ./tests, so these ten cases are in the perf corpus and on the timeline. They are ten of the thirty-five benchmarks there, contributing to any suite aggregate, while measuring engine startup under a name that says prototype access.
Suggested fix
Hoist construction and compilation above b.ResetTimer(), the way tests/bench_test.go already does twenty lines away:
chunk := compileFile(b, ...) // or compile tc.code once
p := driver.NewPaserati()
b.ResetTimer()
for i := 0; i < b.N; i++ { p.InterpretChunk(chunk) }
Two things that make it not quite mechanical:
- the cache configuration is applied via
os.Setenv plus the vm.EnablePrototypeCache / vm.EnableDetailedCacheStats globals inside b.Run, so the hoist has to preserve that ordering relative to construction;
- the workloads are small enough that even a correct version may land under the measurement's attribution floor. It may need a bigger workload rather than only a moved timer — worth measuring before assuming.
BenchmarkPrototypeCacheHitRate has the same shape.
Happy to send a PR if you would like it, though the second point above is a design call I would rather you made.
BenchmarkPrototypeMethodAccessconstructs an engine and compiles its source inside the timed loop, so what it measures is dominated by startup rather than by the prototype access it is named for.The workload for one of the three cases is:
The evidence that startup dominates
From a 16-commit same-host session on a dedicated
c7a.2xlarge, absolutens/opat the final commit:All ten land within 8% of each other at ~1 ms — across three different workloads (string length, array length, prototype chain) and across the three cache configurations
Baseline,WithPrototypeCacheandWithDetailedStats, whose difference is the entire point of the benchmark. If the prototype cache changed anything measurable here,BaselineandWithPrototypeCachewould separate. They do not, because both are ~1 ms ofNewPaserati()plus compiling three tokens.A
b.Nsweep on the same host is consistent with that reading: this group moves a median of −37.8% fromb.N=1 tob.N=256, which is the fixed first-iteration cost being amortised over more iterations. The fivebench_test.gobenchmarks, which compile aboveb.ResetTimer(), are flat over the same sweep.Why it matters
bench-ratchet's default scope ispkg/vmand./tests, so these ten cases are in the perf corpus and on the timeline. They are ten of the thirty-five benchmarks there, contributing to any suite aggregate, while measuring engine startup under a name that says prototype access.Suggested fix
Hoist construction and compilation above
b.ResetTimer(), the waytests/bench_test.goalready does twenty lines away:Two things that make it not quite mechanical:
os.Setenvplus thevm.EnablePrototypeCache/vm.EnableDetailedCacheStatsglobals insideb.Run, so the hoist has to preserve that ordering relative to construction;BenchmarkPrototypeCacheHitRatehas the same shape.Happy to send a PR if you would like it, though the second point above is a design call I would rather you made.