Repository navigation
perf: destroy only the arena nodes that own something (0118 P5b-1) - #436
Merged
Merged
Conversation
Leaf node classes are final and the bases keep protected non-virtual destructors, so a node that owns nothing is trivially destructible and the arena never runs its destructor: Seal and the template's destruction walk a cleanup table of the owners only, which MoveNodes writes. The node-start bitmap moves into the sealed buffer (ArenaView::Checked tests a bit). LookupCache::Forget goes: every render takes its own lookup epoch and keeps the trees it runs alive. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QT4pkibQA9uD34K5vYkC2j
CodeQL flags ptr + n * sizeof(T) as suspicious pointer scaling; the pointers are std::byte, so the arithmetic was right, but naming the offset says so and clears the alert. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QT4pkibQA9uD34K5vYkC2j
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
🤖 Generated with Claude Code
https://claude.ai/code/session_01QT4pkibQA9uD34K5vYkC2j
Before: every arena node has a virtual destructor and an entry in the sealed tree's cleanup table. Seal destroys every original node after moving it, and the template's destruction runs every node's destructor.
ValueRefExpression's destructor also touches the thread's lookup cache (LookupCache::Forget).After: a node that owns nothing is trivially destructible and is never destroyed. Seal and the template's destruction walk a cleanup table of the owners only. Behaviour is unchanged.
This is the first P5b PR (wave 2, plan approved by the perf track). The next ones make the template root, raw text and the remaining names and lists own nothing, after which every non-object node is trivial and a
static_assertenforces it.How:
final.ExpressionEvaluatorBase,IRendererBase,Statement,SetStatement,SetBlockStatementandExpressionRendererhave protected non-virtual destructors. The three bases that still own members and have subclasses (ValueRefExpression,SubscriptExpression,MacroStatement) keep public virtual destructors until P5b-3.IsTriviallyDestructibleNode(node_arena.h) probes a protected destructor through a final subclass. It gives a nulldestroyop, andMakecounts the owners.MoveNodeswrites the owners' offsets into the cleanup table at the end of the sealed buffer. It throwslogic_errorifMakecounted fewer owners than the ops say. Seal's second pass andSealedArena::DestroyNodeswalk only that table.ArenaView::Checkedis a bit test instead of a binary search over the cleanup table, and Seal no longer allocates a heap bitmap for large trees. WithNODEREF_CHECKS=OFFthere is no bitmap, andCheckedskips the object-start test. That check used to run even when OFF; the doc comment already said "at every level but OFF".LookupCache::Forgetand~ValueRefExpressionare deleted. An entry hits only under the epoch it was cached in, every render, context copy and clone takes a new epoch, and a render keeps every tree it runs alive. A tree loaded at a freed tree's addresses is therefore never seen under the freed one's epoch. A Debug assertion checks that a context is used only on the thread whose cache it holds (the perf track accepted this instead of globally unique epochs, which cost Render +0.34..+0.51%). The invariant is documented atLookupCache. The escaped-callable constraint is in the design plan's Escapes section.FullExpressionEvaluator::Rendercalls the baseRenderout of line (RenderEvaluated). Withfinal, inlining it gave the fast path an InternalValue frame (Render/expressions +0.36%).SealKeepsCleanupOnlyForOwners.LookupCacheIgnoresAFreedTemplatesEntries, and the same across threads.Helpers.LookupCacheEntriesEndWithTheirEpochreplaces the twoForgettests.Helpers.LookupCacheIsPerThread.static_assertlists the node kinds that must stay trivial.Numbers (bench/count.py, Release, NODEREF_CHECKS=ON, vs 4fca3f9 = master + 0146)
Render: every case within ±0.1%.
Most node kinds still own a name, a constant or a vector until the next P5b PRs. That is why this PR's saving is small.
Process
-Wabstract-final-classon the trait's probe of the abstract filter and tester interfaces. Classes with a virtual destructor now skip the probe.= deletemembers inStatement, the trait'svaluenaming, and namespace-scope statics in the test.std::byte*+idx * sizeof(T)inOffsetTableandTestSealedBit. The arithmetic was correct (byte pointers), but the second commit names the byte offset first, which clears the alert.Generated by Claude Code