diff --git a/docs/BENCHMARKS.md b/docs/BENCHMARKS.md index df3e0581..f963ad1c 100644 --- a/docs/BENCHMARKS.md +++ b/docs/BENCHMARKS.md @@ -78,7 +78,10 @@ given toolchain, so the gates run in every `go test` without the race detector, which adds allocations of its own. The gates count the allocations of the whole test binary, so each measurement starts after a garbage collection; a collection that earlier tests leave pending would -otherwise add to a figure. The byte ceiling catches a +otherwise add to a figure. No collection runs during a measurement either. +A collection empties the caches of `sync.Pool`, and the measured operation +then allocates what it would have reused, by an amount that depends on when +the collection falls. The byte ceiling catches a single oversized allocation, such as a fixed buffer, which the allocation count does not; each byte ceiling is the measured figure rounded up to the next 64 bytes, because map growth in the in-memory store varies it by a few @@ -93,7 +96,7 @@ figures in the other sections were measured with Go 1.25. | Record StreamWrite of three records | 63 | 7,296 | | JSONL Decode of three records | 33 | 5,504 | | Volume ReadAt within one of ten blocks | 41 | 10,816 | -| Volume Commit of one block onto a small volume | 136 | 252,032 | +| Volume Commit of one block onto a small volume | 135 | 252,032 | A change that exceeds a ceiling fails the test and must justify the increase in its pull request before the ceiling is raised. A change that reduces a diff --git a/internal/testutil/allocgate.go b/internal/testutil/allocgate.go index 0a1b49bc..927d2bb6 100644 --- a/internal/testutil/allocgate.go +++ b/internal/testutil/allocgate.go @@ -2,6 +2,7 @@ package testutil import ( "runtime" + "runtime/debug" "testing" ) @@ -34,7 +35,10 @@ const AllocGateCalls = 1 + (gateRuns + 1) + gateRuns // toolchain, but the race detector adds allocations of its own, so callers // exclude race builds. Each measurement starts after a garbage collection: // the figures count the allocations of the whole test binary, and a -// collection that earlier tests leave pending otherwise adds to them. Each +// collection that earlier tests leave pending otherwise adds to them. No +// collection runs during a measurement either: a collection empties the +// caches of sync.Pool, and the operation then allocates what it would have +// reused, by an amount that depends on when the collection falls. Each // gate logs the ceilings its measured figures call for, so `task gates` // reports them without an edit to the gate table. func RunAllocGates(t *testing.T, gates []AllocGate) { @@ -43,6 +47,9 @@ func RunAllocGates(t *testing.T, gates []AllocGate) { t.Run(g.Name, func(t *testing.T) { op := g.Setup(t) op() + // A collection still runs on runtime.GC, but none starts on its + // own until the gate restores the collector's target. + defer debug.SetGCPercent(debug.SetGCPercent(-1)) runtime.GC() allocs := testing.AllocsPerRun(gateRuns, op) runtime.GC() diff --git a/lode/volume/alloc_test.go b/lode/volume/alloc_test.go index f8536b42..84abf7a4 100644 --- a/lode/volume/alloc_test.go +++ b/lode/volume/alloc_test.go @@ -24,7 +24,7 @@ const ( // in-memory store varies it by a few bytes. var allocGates = []testutil.AllocGate{ {Name: "ReadAt within one of ten blocks", Allocs: 41, Bytes: 10816, Setup: gateReadAt}, - {Name: "Commit of one block onto a small volume", Allocs: 136, Bytes: 252032, Setup: gateCommit}, + {Name: "Commit of one block onto a small volume", Allocs: 135, Bytes: 252032, Setup: gateCommit}, } // TestAllocationGates holds each Volume hot path to its allocation ceiling.