diff --git a/docs/tasks/0146-load-settings-fast-path.md b/docs/tasks/0146-load-settings-fast-path.md index 419aeed7..f4e77188 100644 --- a/docs/tasks/0146-load-settings-fast-path.md +++ b/docs/tasks/0146-load-settings-fast-path.md @@ -1,5 +1,5 @@ --- -status: in-progress +status: done priority: medium area: perf depends: [0131] @@ -29,3 +29,7 @@ for_range 2.9%, filters 0.6%. Split out of the 0118 P5 plan (perf track review, 3cc8e7d: plain_text -5.75%, substitute -2.36%, for_range -1.25%, inheritance -0.71%, other cases -0.0..-0.6%; Render unchanged. Option 1 waits on Ruslan, and the perf track asks for it only if plain_text is still above its 0892811 count (4,121) after 0118 P5. +- Option 2 merged in #435 (6039634). The perf track no longer needs option 1: 0118 P5b-2 + (#437) brought Load/plain_text to 4,077, under its 0892811 count. Option 1 (the + generation counter and the 2.0 deprecation) stays an API choice for Ruslan, and gets its + own task if he takes it. diff --git a/docs/tasks/0147-macro-call-residual-cost.md b/docs/tasks/0147-macro-call-residual-cost.md new file mode 100644 index 00000000..bd64f6bc --- /dev/null +++ b/docs/tasks/0147-macro-call-residual-cost.md @@ -0,0 +1,32 @@ +--- +status: open +priority: low +area: perf +depends: [0117] +touches: [src/statements.cpp, src/render_workspace.cpp, include/jinja2cpp/user_callable.h] +--- +# What a macro call still costs after its arguments moved to slots + +**Problem.** After 0117 P2 (#434) Render/macros fell 13.5%, short of the plan's +-20..-25%. A profile of `MacroStatement::InvokeMacroRenderer` on the `macros` case +(callgrind, `--dump-instr=yes`, self cost by source line) puts the rest of a call, besides +rendering the body, at: +- about 230 instructions copying each argument out of the `const CallParams&` into its + slot (variant copies and shared_ptr reference counts); the call site owns the + evaluated arguments and could move them, but the callable signature takes them by + const reference; +- about 100 taking and giving back the frame (`RenderWorkspace::Take`/`Release`); +- about 140 checking the arguments (`CheckMacroCallArgs`, the `any_of` over defaults, the + keyword pre-lookup), most of which depends only on the macro and could be computed at + Load. + +**Proposal.** Measure each separately: pass evaluated arguments by rvalue to macro +callables (an internal overload; user callables keep theirs), precompute the per-macro +flags at Load, and a frame fast path for a unit whose frame is the same size as the +previous call's. Filed at the perf track's request (2026-10-08). + +**API note for Ruslan.** The rvalue path is meant to stay internal: macros and other +callables the engine makes get an extra overload that takes the evaluated arguments by +value or rvalue. The public `user_callable.h` signatures are unchanged. It is listed in +`touches` only because the internal overload may sit next to the public one. If it can +live entirely in src/, the public header drops out of `touches`. diff --git a/docs/tasks/0148-single-use-names-skip-cache.md b/docs/tasks/0148-single-use-names-skip-cache.md new file mode 100644 index 00000000..a84b68cb --- /dev/null +++ b/docs/tasks/0148-single-use-names-skip-cache.md @@ -0,0 +1,27 @@ +--- +status: open +priority: low +area: perf +depends: [0117, 0118] +touches: [src/expression_evaluator.h, src/expression_evaluator.cpp, src/name_resolver.cpp, src/render_context.h] +--- +# Skip the lookup cache for names that are read once per render + +**Problem.** The lookup cache keys an entry by the name node and the render's epoch, and +every render takes a new epoch (0118 P5b deleted the cache's `Forget`). A name node +outside any loop, macro body or call block runs at most once per render, so it always +misses: the cache only costs it the probe and the insert. After 0118 P5b-3a a miss also +reads the name through the tree's view, 3 instructions more than before (a hit 3 fewer). +Render/large_static (50 `{{ title }}` nodes, all outside loops) rose 0.55%, inheritance +0.43%; the perf track accepted that as a miss/hit trade (suite Render -0.16%) on condition +that this task is filed. + +**Proposal.** The name resolver knows at parse time whether a use sits inside a loop, a +macro or a call body (its frames). Set a flag on `ValueRefExpression` for the uses that do +not, and let them go straight to `FindValue`, skipping the cache slot (and its `NewSlot`). +Measure Render on flat templates (large_static, substitute, config_file, inheritance), and +check that loops do not lose: a macro body is re-entered per call and an included template +per include, so those stay cached. + +**Done when** the flat cases' Render falls by at least the P5b-3a miss cost and no case +rises past 0.3%. diff --git a/docs/tasks/README.md b/docs/tasks/README.md index 143c5f4e..0ea97162 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -211,3 +211,7 @@ files go under `touches` (0012 and 0024 each rewrite about a hundred rows). | [0142](0142-single-pass-scanner.md) | Load scans the source twice | perf | low | open | | [0143](0143-self-reference-leftovers.md) | `self` leftovers after 0139 | parity | low | open | | [0144](0144-loop-cycle-sentinel.md) | `loop.cycle` is the integer 2 | parity | medium | open | +| [0145](0145-callable-identity-equality.md) | A macro or a callable is not equal to itself | parity | low | open | +| [0146](0146-load-settings-fast-path.md) | Load compares the whole Settings on every template | perf | medium | done | +| [0147](0147-macro-call-residual-cost.md) | What a macro call still costs after its arguments moved to slots | perf | low | open | +| [0148](0148-single-use-names-skip-cache.md) | Skip the lookup cache for names that are read once per render | perf | low | open |