+74
−2
+106
−12
Loading
Extend page retention beyond the per-class carve target: a region that
would be returned to the OS - a drained shared page whose class already
has a carve target, or a page backing a single big allocation - now
goes into a pool keyed by region size and is reused by later mappings
of that size, avoiding the mmap/munmap round trip and the page faults
of first touch. Big-allocation turnover is the main syscall source
left after the carve-target retention: workloads that repeatedly build
and free large buffers (SQLite's JSON functions, for one) pay a
dedicated mmap and munmap per buffer.
Two measures make the pool actually hit. Region sizes are collapsed
into classes - every request up to pageSize maps a full 64 KiB region,
larger requests round up to a power of two on 64-bit hosts - so
similar-but-unequal sizes reuse each other's regions; the extra
address space is never touched and so costs nothing. And the pool is
bounded by 4 MiB plus the high-water mark of the live mapping rather
than the current one: at the moment a large transient buffer is freed
the live size has already shrunk by that buffer, and it is exactly the
region most worth retaining.
Pooled regions stay in regs and in the counters; Trim returns them to
the OS along with the carve targets, and Close already unmaps
everything in regs. TestNoTrimBigPage asserted that big-allocation
pages are unmapped at once and becomes TestTrimBigPage, asserting the
retention and that Trim releases it; TestFreedPool covers reuse.
Replaying speedtest1 through modernc.org/sqlite (benchstat n=10,
linux/amd64, github.com/josharian/benchmosql):
json 380.1m -> 143.9m -62.1%
orm 163.5m -> 126.4m -22.7%
cte 487.0m -> 442.0m -9.3%
main 384.1m -> 364.7m -5.1%
star 26.4m -> 25.2m -4.7%
fp 306.0m -> 292.9m -4.3%
app 74.3m -> 75.4m +1.4%
geomean -18.8%