Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/tasks/0146-load-settings-fast-path.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
status: in-progress
status: done
priority: medium
area: perf
depends: [0131]
Expand Down Expand Up @@ -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.
32 changes: 32 additions & 0 deletions docs/tasks/0147-macro-call-residual-cost.md
Original file line number Diff line number Diff line change
@@ -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`.
27 changes: 27 additions & 0 deletions docs/tasks/0148-single-use-names-skip-cache.md
Original file line number Diff line number Diff line change
@@ -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%.
4 changes: 4 additions & 0 deletions docs/tasks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Loading