mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-22 15:37:59 +00:00
A StackFrameSet lives on the persistent runtime.State and is reused across all records, but every block entry (StatementBlockNode.Execute does PushStackFrame/PopStackFrame, which runs once per record for the main block, plus once per if/for/etc.) allocated a fresh StackFrame -- a []*var slice and a map[string]int -- and discarded it on exit. For `put`/`filter` that is millions of throwaway allocations. Since push/pop is strictly LIFO, retain popped frames in a per-frameset free list and clear-and-reuse them on the next push. After the first record establishes the max block-nesting depth, per-record block execution is allocation-free for frames. len(stackFrames) remains the logical depth, so get/set/defineTyped/unset/etc. are unchanged. Measured (big.csv, 1M rows, best of 4): put chain-1 0.78 -> 0.72 (~8%) put chain-4 0.96 -> 0.87 (~9%) Allocation objects for put chain-1 drop ~23.1M -> ~20.0M (the per-record newStackFrame churn, ~2.86M, is eliminated). UDF calls still allocate a fresh frameset per call (PushStackFrameSet); pooling those is a separate change. The dominant remaining DSL allocator is FromFloat (~6.8M, interior arithmetic temporaries); eliminating it needs node-owned result slots + in-place bif variants, a much larger and aliasing-sensitive change, left for follow-up. Verified: go test ./pkg/... and full regression suite pass; put output is byte-identical, including UDFs with locals/loops/blocks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| doc.go | ||
| README.md | ||
| stack.go | ||
| state.go | ||
Contains state for the CST executor.