Reuse scalar argument vectors across range-function steps
perfloop/prometheus · ALLOCATION HOT LOOP
https://perfloop.ai/t/oss/case_8z09apkgmn
Verdict
VERIFIED · settled 2026-08-16
What happened: The paired measurements met the required improvement.
Hypothesis
In promql.evaluator.eval's matrix-argument branch, line 2301 constructs Vector{Sample{F: ...}} for every non-matrix argument inside the selected-series × range-step loop before the call at line 2350. Thus a range query such as double_exponential_smoothing(a_hundred[1d], 0.3, 0.3) constructs two one-sample vectors per selected series per step: 100 series × 1001 steps × 2 scalar arguments in the existing benchmark. The focused compiler check `go test -run='^$' -gcflags='-m=2' ./promql` reported that the Vector literal at engine.go:2301 escapes to the heap. A one-iteration existing range-query benchmark measured 224,963 allocs/op and 15,067,096 B/op; an ephemeral preallocation probe measured 24,758 allocs/op and 5,457,176 B/op, removing about 200,205 allocations and 9.6 MB for that trigger. The single-iteration elapsed time was effectively unchanged (6.62 s), so an end-to-end latency gain remains hypothesized rather than measured. The cadence is once per non-matrix argument for every selected series and every returned range step, which can scale to the query_range endpoint's large per-series step counts. Proof target: rerun `go test -run='^$' -bench='^BenchmarkRangeQuery/expr=double_exponential_smoothing\(a_hundred\[1d\],_0\.3,_0\.3\),steps=1000$' -benchtime=10x -benchmem ./promql` plus result-equivalence tests after the change; it should retain identical results while reducing allocs/op and alloc-space, and an alloc_objects profile should no longer attribute one object per scalar-slot/series/step to engine.go:2301.
Change to test: Preallocate one one-sample Vector backing store for each non-matrix argument before the series loop, overwrite its complete Sample for each series/step, and pass those ephemeral slots to the function call instead of constructing Vector{Sample{...}} each time; retain the existing requirement that function implementations do not retain or mutate their input arguments.
Where it lives
perfloop/prometheus · web/api/v1/api.go
Evidence
100-series, 1001-step double_exponential_smoothing subquery through /api/v1/query with two scalar arguments · 10 sample pairs
| metric | baseline | candidate | paired median change | confidence range | required | result |
|---|---|---|---|---|---|---|
B/op |
16527457 |
6920703 |
−58.1% (−9608566) |
−9614646 to −9596636 |
< 0 |
PASSED |
allocs/op |
210377 |
10363 |
−95.1% (−200015) |
−200020 to −200009 |
< 0 |
PASSED |
Checks: 4 of 4 passed. Verification: no defect found.
Timeline
2026-08-04· Case opened2026-08-10· PR opened2026-08-21· Case closed