Loading
sqlite: pool the FunctionContext handed to UDF and aggregate callbacks
Updates #226 (closed).
Problem
After !114 (merged) pooled the []driver.Value slice, the &FunctionContext{} passed to Scalar, Step, WindowInverse, WindowValue and Final was the last driver-side heap allocation per invocation: a 16-byte object escaping to the heap on every call, about 12% of the allocations left in the #226 (closed) reproducer, the same magnitude !114 (merged) saved.
Change
- Fold the context into the pooled per-call object next to the args slice (
udfCall), so onesync.PoolGet/Put serves both. The five UDF trampolines hand the callback&call.ctx. - Populate the context's
tlsandsqlite3_contextfields per call (they were always zero at these sites before), so accessor methods can be added toFunctionContextlater without touching the trampolines.makeAggregatealready did this for the by-value context given toMakeAggregate. - The vtab
FilterandUpdatepaths use the same pooled object with a zero context; what they hand to modules is unchanged. - Document on
FunctionContextand on the callbacks that, like the argument slice, the context is valid only for the duration of the call and must not be retained. Nothing observable changes for existing callers: the type has no exported fields or methods, so the only way to notice the reuse is comparing pointers across calls.
Numbers
Same machine and flags for both columns (linux/amd64, go1.27, -count 3):
| Benchmark | master | this MR |
|---|---|---|
| BenchmarkUDFArgsAllocation | 5756 allocs/op, 70489 B/op, ~572 us/op | 4756 allocs/op, 54476 B/op, ~561 us/op |
| BenchmarkUDFArgsAllocationVolatile | 3756 allocs/op, 62481 B/op, ~512 us/op | 2756 allocs/op, 46464 B/op, ~505 us/op |
| #226 (closed) reporter's main_test.go | 25.33M allocs/op, 553 MB/op, ~169 GCs | 22.35M allocs/op, 505 MB/op, ~154 GCs |
Exactly 1000 allocations per 1000 UDF calls removed, as expected; ns/op unchanged within noise.
Tests
- New
TestFunctionContextPerCallchecks that every callback (scalar, Step, WindowInverse, WindowValue, Final) receives a non-nil, populated context, through a scalar, an aggregate and a window query. - The UDF, aggregate, window, vtab, volatile-args and hook tests pass with and without
-race;go vetreports the same set of pre-existing notes as master; cross-builds for windows/386 and freebsd/arm pass;-gcflags=-mshows no heap escape at the callback call sites.
CHANGELOG
A pending v1.59.0 section is included per HACKING.md.