Skip to content

Cap nested section expansion with the single-copy variant - #233

Open
KristofferC wants to merge 1 commit into
masterfrom
kc/nested-sections-single-copy
Open

Cap nested section expansion with the single-copy variant#233
KristofferC wants to merge 1 commit into
masterfrom
kc/nested-sections-single-copy

Conversation

@KristofferC

Copy link
Copy Markdown
Owner

@timeit splices its body into an enabled (timed, try/finally) branch and a disabled (bare) branch. Lexically nested sections therefore compound: depth d produces 2^d copies of the innermost code. This is easy to hit with nested @timed_testsets or @timeit_all over nested control flow — at depth 7 one small function took ~2.8 s of first-call compile time and ~8 MB of LLVM IR.

This PR routes any body that lexically contains another section to single_copy_section (the variant #230 introduced for @label-defining bodies), so only the innermost section duplicates and total code growth is capped at 2×. Nesting is detected as:

  • an unexpanded @timeit/@timeit_debug/@timeit_all/@timed_testset macrocall, or
  • for bodies that are already expanded when the outer section wraps them (function definitions, which are macroexpanded first, and @timeit_all's bottom-up statement instrumentation): the interpolated isenabled function object every expanded section contains.

The detection is a compile-time heuristic only — a miss (e.g. a user macro that expands to @timeit) just forgoes the saving; a false positive just costs a try/finally.

Why keep duplication at all (benchmarked on 1.10.11 / 1.11.9 / 1.12.6 / 1.13.0-rc1)

Julia 1.11+ elides the exception frame only for provably nothrow bodies. For realistic (may-throw) bodies the try frame survives on all versions, costing ~4–9 ns per disabled-section entry vs ~0.3–0.6 ns with the duplicated bare branch, and NoTimerOutput is only truly zero-overhead with duplication. So single-copy everywhere is not an option; capping the nesting is.

Effect (Julia 1.12.6, first-call compile time of one function with nested sections)

depth before after
3 74 ms 31 ms
5 541 ms 58 ms
7 2782 ms 93 ms

Non-nested sections are unchanged (still duplicated; their compile cost is dominated by the timing machinery itself, the extra bare copy is ~5–10%).

The trade: outer levels of a nest keep the single-copy try frame even when the timer is disabled / NoTimerOutput / debug-off (~4–8 ns per entry); the innermost, hottest section still folds away as before.

Tests: macroexpansion-level assertions that 3-deep @timeit, a @timeited function with an inner section, @timeit_all over nested loops, and nested @timed_testsets all produce exactly 2 copies of the innermost code, plus semantic checks (nested counting, break across the single-copy boundary, disabled timer). Full suite passes on 1.12; the new testset also passes on 1.10.

🤖 Generated with Claude Code

A @timeit section splices its body into an enabled (timed) and a disabled
(bare) branch, so lexically nested sections compound: depth d produces 2^d
copies of the innermost code. With 7 nested sections (easy to reach with
nested @timed_testsets or @timeit_all over nested loops) that was ~2.8 s of
first-call compile time and ~8 MB of LLVM IR for one small function.

Route any body that lexically contains another section to
single_copy_section, the variant already used for @label-defining bodies:
detected as an unexpanded @timeit-family macrocall, or — for bodies expanded
before wrapping (function definitions, @timeit_all's per-statement
instrumentation) — as the interpolated isenabled function object every
expanded section contains. Only the innermost section duplicates, capping
total growth at 2x: the depth-7 case drops to ~90 ms / ~390 KB, while
non-nested sections are unchanged. The detection is a compile-time heuristic
only: a miss forgoes the saving, a false positive costs a try/finally.

The trade: outer levels of a nest keep the single-copy try frame even when
the timer is disabled (or is a NoTimerOutput / debug-off @timeit_debug),
~4-8 ns per entry, since the exception handler is only elided for provably
nothrow bodies. The innermost, hottest section still folds away as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant