feat(evolution): 🔍 attribute the layer build behind monoprop_LAYER_PROFILE - #232
feat(evolution): 🔍 attribute the layer build behind monoprop_LAYER_PROFILE#232diagonal-hamiltonian wants to merge 1 commit into
Conversation
…OFILE
Before this there was no way to say where a layer build's time went. The
instrumented phases summed to ~6% of wall, so a phase split described
almost nothing and every question about the engine had to be answered
with the end-to-end wall -- an instrument whose observed spread on
build_graph is 2.86x, and which needs ~1590 paired repetitions to
resolve a 3% effect. The phase timers resolve the same 3% in about 5.
Two instruments with deliberately different costs:
* phase timers and per-gate counters -- O(1) clock reads per gate, with
per-term counters kept in locals and folded into the slot once per
gate so the emit loop keeps its registers;
* a store population sweep (k/d histograms, paired and overflow
fractions) -- O(n), so it runs only on power-of-two gate indices,
which also yields the population's growth curve for free.
`layer_ns` brackets the WHOLE of build_layer, so layer_ns minus the
phases is the unattributed remainder. Without that the split is not
falsifiable: phases that sum to a small fraction of wall can be
individually accurate and collectively meaningless. `index_ns` covers
the inverted-index rebuild, which is not a layer phase but is charged
per gate and had never been attributed to anything.
Slots are heap-allocated per thread and owned by the registry, never
freed: a partition master can outlive or predecease the dump, and a
dangling slot would be a use-after-free in a diagnostic. The registry
dumps from its destructor rather than via atexit so the ordering is
sound -- slot() evaluates config::get() before Registry::add(), so
Settings is constructed first and therefore destroyed last.
Off by default and it changes no result: slot() returns nullptr when the
knob is unset, so every call site is one predictable branch and no clock
is read. Output goes to stderr, which pytest's fd-level capture
discards without `-s`.
Assisted-by: ClaudeCode:claude-opus-5
|
Docs preview: https://pr-232.monoprop-docs.pages.dev |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #232 +/- ##
=======================================
Coverage 97.70% 97.70%
=======================================
Files 14 14
Lines 742 742
Branches 98 98
=======================================
Hits 725 725
Misses 12 12
Partials 5 5
Flags with carried forward coverage won't be shown. Click here to find out more. |
|
|
🤖 AI text below 🤖 Closing this stack to rebuild it around a single-path design. Not abandoned — the work returns as fresh PRs once it is ready. Why. The stack accumulated runtime knobs as a way to A/B competing paths, and an A/B knob is not a shipping knob. Against
What the measurements decided. Several paths were being kept behind default-off knobs on the strength of stale or never-run comparisons:
What returns. The sparse query record together with the positions-based resolve (this PR's successor), the self-resolve simplification, and the layer profiler. Roughly 1,800 lines are deleted rather than added, and every PR in the new stack is a pure addition — the intermediate record format is never introduced, so nothing is added and then removed. Reopening as new PRs once the local work is verified. |



🤖 AI text below 🤖
Summary
Adds
monoprop_LAYER_PROFILE: a per-partition attribution of the layer build, off by default.Before this there was no way to say where a layer build's time went. The instrumented phases summed to ~6% of wall, so a phase split described almost nothing, and every question about the engine had to be answered with the end-to-end wall — an instrument whose observed spread on
build_graphis 2.86×, and which needs ~1590 paired repetitions to resolve a 3% effect. The phase timers resolve the same 3% in about 5.This is the instrument the rest of this work is measured with, and it is useful on its own for anyone profiling the engine.
Changes
Two instruments with deliberately different costs:
layer_nsbrackets the whole ofbuild_layer, solayer_nsminus the phases is the unattributed remainder. Without it the split is not falsifiable: phases that sum to a small fraction of wall can be individually accurate and collectively meaningless.index_nscovers the inverted-index rebuild, which is not a layer phase but is charged per gate and had never been attributed to anything.Slots are heap-allocated per thread and owned by the registry, never freed: a partition master can outlive or predecease the dump, and a dangling slot would be a use-after-free in a diagnostic. The registry dumps from its destructor rather than via
atexitso the ordering is sound —slot()evaluatesconfig::get()beforeRegistry::add(), soSettingsis constructed first and therefore destroyed last.Cost when off
slot()returnsnullptrwhen the knob is unset, so every call site is one predictable branch and no clock is read. It changes no result.Output goes to stderr, which pytest's fd-level capture discards without
-s. Documented indocs/content/docs/features/parallelism.mdx.Verification
ctest -L serial— 208/208 green.pytest tests --with-mpigreen at 1, 2 and 4 ranks.Checklist
docs/,CONTRIBUTING.md) if neededCHANGELOG/ release notes updated if applicable — release notes are generated from the PR titleAI/LLM disclosure