From 69978f994fa3fd665ac034b2ab25cc374e9609ff Mon Sep 17 00:00:00 2001 From: Robert Sayre Date: Thu, 16 Apr 2026 16:16:07 -0700 Subject: [PATCH] core_matcher: pool nfaBuffers and flattener in the test helper path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit matchesForJSONWithFlattener previously allocated a fresh *nfaBuffers per call, and matchesForJSONEvent additionally allocated a fresh *flattenJSON per call. These helpers are the path used by most tests and benchmarks (the production *Quamina.MatchesForEvent already reuses both). Add two package-level sync.Pools and Get/Put on each call. On Put, re-seat bufs.resultBuf with a fresh slice so the []X returned to the caller can't be written through by the next pool user — the old backing array stays exclusive to the caller. Bench (Apple M1 Ultra, n=6): NumberMatching-20 890.2n -> 498.3n -44.03% 2288 B -> 280 B -87.76% 10 allocs -> 3 allocs geomean (10 benchmarks): -4.71% time, -18.39% B/op Other benchmarks that route through *Quamina.MatchesForEvent rather than the helpers are unchanged in behavior; a few show +1-3% noise likely from code layout (they don't touch the pooled path). Co-Authored-By: Claude Opus 4.7 (1M context) --- core_matcher.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/core_matcher.go b/core_matcher.go index 08d6d43..acb7b26 100644 --- a/core_matcher.go +++ b/core_matcher.go @@ -132,16 +132,31 @@ func (m *coreMatcher) deletePatterns(_ X) error { // This is a leftover from previous times, is only used by tests, but it's used by a *lot* // and it's a convenient API for testing. func (m *coreMatcher) matchesForJSONEvent(event []byte) ([]X, error) { - return m.matchesForJSONWithFlattener(event, newJSONFlattener()) + f := flattenerPool.Get().(*flattenJSON) + defer flattenerPool.Put(f) + return m.matchesForJSONWithFlattener(event, f) } // if your test is a benchmark, call newJSONFlattener and pass it to this routine, matchesForJSONWithFlattener // because newJSONFlattener() is fairly heavyweight and you want it out of the benchmark loop func (m *coreMatcher) matchesForJSONWithFlattener(event []byte, f Flattener) ([]X, error) { fields, _ := f.Flatten(event, m.getSegmentsTreeTracker()) - return m.matchesForFields(fields, newNfaBuffers()) + bufs := nfaBuffersPool.Get().(*nfaBuffers) + defer func() { + // Detach the result slice from bufs before returning to the pool: the + // returned []X aliases bufs.resultBuf's backing array, and a subsequent + // pool user must not be able to write through it. + bufs.resultBuf = make([]X, 0, 16) + nfaBuffersPool.Put(bufs) + }() + return m.matchesForFields(fields, bufs) } +var ( + nfaBuffersPool = sync.Pool{New: func() any { return newNfaBuffers() }} + flattenerPool = sync.Pool{New: func() any { return newJSONFlattener().(*flattenJSON) }} +) + // emptyFields returns a fake []Field list containing a single field whose name is lexically greater than any that // can occur in real data // see the commentary on coreMatcher for an explanation.