You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
this was my clanker trying out things, I have not looked at it yet, just wanted CI to run.
Description
This draft builds on #4045 and adds a thread-local sparse shadow bitmap to the production LocalLiveHeapTracker. ZendMM's eight-byte-aligned pointer address is split into a four-kilobyte application-page number and an exact slot bit; a six-level radix tree lazily maps the page number to a 64-byte bitmap covering all 512 possible allocation starts on that page.
A bit is set only after a sampled allocation is successfully inserted into the existing live-heap DashMap. Free and successful realloc test and clear that exact bit first: a clear bit skips the map lookup, while a set bit removes the full LiveHeapSample. The DashMap remains the source of stack, size, label, and profile metadata.
Pointers pass through unchanged. This removes the allocation prefix, alignment overhead, clean-epoch state, startup and late-thread ZendMM resets, TSRM singleton callback, and virtual-CWD lifecycle handling. Pre-hook allocations, neighboring custom allocators, USE_ZEND_ALLOC=0, and every supported PHP version use the same pointer-preserving path.
The shadow map lives inside the existing explicitly initialized and destroyed allocation-profiling TLS. Radix nodes use Rust's system allocator rather than ZendMM, avoiding allocator-hook recursion. Empty radix leaves are deliberately retained until thread teardown; pruning can be added if long-lived heaps show unbounded metadata growth.
The PR also documents allocation-footer and ZendMM chunk-map alternatives.
Benchmark
Criterion calls the production LocalLiveHeapTracker::track and untrack methods with 2,048 existing 32-byte samples. It times batches of 256 calls so fixture mutation and per-operation timer overhead are excluded. Results on an Apple M4 Max:
Path
DashMap only
Shadow bitmap
Change
Allocate tracked
5.484–5.504 ns
6.864–6.893 ns
+24.7%
Free tracked
5.512–5.539 ns
7.059–7.098 ns
+28.4%
Free untracked
2.634–2.651 ns
1.506–1.514 ns
-43.1%
The measured bookkeeping breaks even at roughly three untracked frees per tracked allocation/free lifecycle; allocation sampling makes the expected ratio substantially higher.
Validation
Profiler Rust unit tests: 23 passed, 0 failed.
Shadow bitmap and ext-parallel PHPTs passed on PHP 8.5 ZTS amd64.
Profiler clippy passed with the unrelated pre-existing got_macho.rs lint suppressed.
Comparing candidate commit 6abe5ab in PR branch florian/no-jira-heap-live-prefix-poc with baseline commit 9a0cf90 in branch florian/no-jira-heap-live-tracking-benchmark.
Found 0 performance improvements and 0 performance regressions! Performance is the same for 27 metrics, 9 unstable metrics.
Explanation
This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:
🟩 = significantly better candidate vs. baseline
🟥 = significantly worse candidate vs. baseline
We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.
If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.
Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.
More details about the CI and significant changes
You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.
CIs of the difference of means are often centered around 0%, because often changes are not that big:
---------------------------------(------|---^--------)-------------------------------->
-0.6% 0% 0.3% +1.2%
| | |
lower bound of the CI --' | |
sample mean (center of the CI) -------------' |
upper bound of the CI ----------------------'
As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).
For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:
----------------------------------------|---------|---(---------^---------)---------->
0% 1% 1.3% 2.2% 3.1%
| | | |
significant impact threshold --------------' | | |
lower bound of CI --------------' | |
sample mean (center of the CI) --------------------------' |
upper bound of CI ----------------------------------'
realFlowControl
changed the title
feat(profiling): prototype heap-live allocation prefixes
feat(profiling): prototype heap-live shadow bitmap
Jul 17, 2026
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
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.
Note
this was my clanker trying out things, I have not looked at it yet, just wanted CI to run.
Description
This draft builds on #4045 and adds a thread-local sparse shadow bitmap to the production
LocalLiveHeapTracker. ZendMM's eight-byte-aligned pointer address is split into a four-kilobyte application-page number and an exact slot bit; a six-level radix tree lazily maps the page number to a 64-byte bitmap covering all 512 possible allocation starts on that page.A bit is set only after a sampled allocation is successfully inserted into the existing live-heap
DashMap. Free and successful realloc test and clear that exact bit first: a clear bit skips the map lookup, while a set bit removes the fullLiveHeapSample. TheDashMapremains the source of stack, size, label, and profile metadata.Pointers pass through unchanged. This removes the allocation prefix, alignment overhead, clean-epoch state, startup and late-thread ZendMM resets, TSRM singleton callback, and virtual-CWD lifecycle handling. Pre-hook allocations, neighboring custom allocators,
USE_ZEND_ALLOC=0, and every supported PHP version use the same pointer-preserving path.The shadow map lives inside the existing explicitly initialized and destroyed allocation-profiling TLS. Radix nodes use Rust's system allocator rather than ZendMM, avoiding allocator-hook recursion. Empty radix leaves are deliberately retained until thread teardown; pruning can be added if long-lived heaps show unbounded metadata growth.
The PR also documents allocation-footer and ZendMM chunk-map alternatives.
Benchmark
Criterion calls the production
LocalLiveHeapTracker::trackanduntrackmethods with 2,048 existing 32-byte samples. It times batches of 256 calls so fixture mutation and per-operation timer overhead are excluded. Results on an Apple M4 Max:The measured bookkeeping breaks even at roughly three untracked frees per tracked allocation/free lifecycle; allocation sampling makes the expected ratio substantially higher.
Validation
got_macho.rslint suppressed.cargo fmt --checkandgit diff --checkpassed.Reviewer checklist