Skip to content

perf(profiling): remove indirect allocator forwarding - #4070

Merged
morrisonlevi merged 11 commits into
masterfrom
florian/prof-15506-faster-allocation-forwarding
Jul 28, 2026
Merged

perf(profiling): remove indirect allocator forwarding#4070
morrisonlevi merged 11 commits into
masterfrom
florian/prof-15506-faster-allocation-forwarding

Conversation

@realFlowControl

@realFlowControl realFlowControl commented Jul 27, 2026

Copy link
Copy Markdown
Member

Description

Forward allocations, reallocations, and frees from one ZendMMState read instead of indirect Rust calls and repeated profiler-global lookups. During RINIT, select compile-time-specialized normal and neighboring-allocator callbacks for malloc, free, and realloc so the common paths have no previous-handler load or branch; neighboring callbacks remain cold compatibility paths. Free and realloc callbacks also specialize heap-live tracking behavior.

PHP 8.4+ calls ZendMM directly. PHP 8.3 and older retains the required prepare_zend_heap() / restore_zend_heap() calls. Test-only _zend_mm_free stubs keep the direct callbacks linkable outside PHP.

Benchmark

Fresh release builds from master (a732d1203) and this branch (010c2f717), using six balanced 60-second runs per binary on macOS ARM64:

PHP master mean candidate mean mean median
8.3 ZTS 28,923,420/s 33,094,543/s +14.42% +14.57%
8.3 NTS 42,983,480/s 42,726,419/s -0.60% -0.67%
8.5 NTS 9,644,926/s 10,207,814/s +5.84% +6.21%

The PHP 8.3 NTS control remains neutral while PHP 8.3 ZTS improves by 14.42%, indicating that the legacy-path gain primarily comes from eliminating repeated TSRM-backed profiler-global lookups. PHP 8.5 NTS also improves because RINIT-selected callbacks remove previous-handler dispatch from free and realloc. Fresh 70-second native samples reduced selected Rust malloc/free forwarding top-of-stack samples by 37% on PHP 8.3 ZTS and 38% on PHP 8.5 NTS.

Validation: release builds, 22 Rust tests on PHP 8.3/8.5, five allocation PHPTs on PHP 8.3/8.5, PHP 7.3 ZTS Docker validation, and cargo test --all-features on PHP 8.5 NTS Linux/macOS (24 passed).

Reviewer checklist

  • Test coverage seems ok.
  • Appropriate labels assigned.

https://datadoghq.atlassian.net/browse/PROF-15506

Read executor_globals.current_execute_data directly on NTS builds instead of calling through the C FFI wrapper for every allocation and reallocation. Keep the existing wrapper on ZTS builds.

A 60-second macOS sample reduced get_current_execute_data self time from 0.412% (207/50,210 main-thread samples) to 0.002% (1/49,737). Known allocation-hook self time fell from 4.184% to 3.601%, a 13.9% relative reduction. Repeated end-to-end throughput remained within system scheduling noise.

Validation: cargo test (22 passed); allocation sampling-distance, memory-peak, and GC PHPTs passed.
@realFlowControl realFlowControl added the profiling Relates to the Continuous Profiler label Jul 27, 2026
@datadog-datadog-prod-us1-2

datadog-datadog-prod-us1-2 Bot commented Jul 27, 2026

Copy link
Copy Markdown

Pipelines  Tests

Unblock PR with BitsAI

⚠️ Warnings

🚦 9 Pipeline jobs failed

DataDog/apm-reliability/dd-trace-php | ASAN test_c with multiple observers: [8.5]   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-php | ASAN test_c: [7.4, arm64]   View in Datadog   GitLab

DataDog/apm-reliability/dd-trace-php | ASAN test_c: [8.0, arm64]   View in Datadog   GitLab

View all 9 failed jobs.

ℹ️ Info

No other issues found (see more)

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 60.65% (-0.02%)

Useful? React with 👍 / 👎

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: 010c2f7 | Docs | Datadog PR Page | Give us feedback!

@pr-commenter

pr-commenter Bot commented Jul 27, 2026

Copy link
Copy Markdown

Benchmarks [ profiler ]

Benchmark execution time: 2026-07-28 18:27:32

Comparing candidate commit 010c2f7 in PR branch florian/prof-15506-faster-allocation-forwarding with baseline commit a732d12 in branch master.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 29 metrics, 7 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 ----------------------------------'

Forward PHP 8.4+ allocations and frees from a single ZendMMState read instead of loading a function pointer, making an indirect Rust call, and reloading the state. Preserve neighboring custom allocator support with a predictable previous-handler check.

Use unchecked heap extraction in the callbacks: rinit stores the heap before they can be invoked, and rshutdown removes them before clearing it. This avoids an Option discriminant check on every allocation and free.

Across six 60-second runs per binary, mean allocation throughput increased from 9,366,444/s to 10,018,262/s (+6.96%) and median throughput increased by 7.56%. A 60-second native sample reduced known forwarding-path self time from 3.599% to 2.438% (-32.3% relative).

Validation: cargo test (22 passed); allocation sampling-distance, memory-peak, and GC PHPTs passed.

https://datadoghq.atlassian.net/browse/PROF-15506
@realFlowControl
realFlowControl force-pushed the florian/prof-15506-faster-allocation-forwarding branch from efd1c5e to 6e87883 Compare July 27, 2026 16:28
@pr-commenter

pr-commenter Bot commented Jul 27, 2026

Copy link
Copy Markdown

Benchmarks [ tracer ]

Benchmark execution time: 2026-07-28 19:29:43

Comparing candidate commit 010c2f7 in PR branch florian/prof-15506-faster-allocation-forwarding with baseline commit a732d12 in branch master.

Found 2 performance improvements and 2 performance regressions! Performance is the same for 190 metrics, 0 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 ----------------------------------'

scenario:MessagePackSerializationBench/benchMessagePackSerialization

  • 🟩 execution_time [-6.864µs; -5.156µs] or [-6.114%; -4.593%]

scenario:MessagePackSerializationBench/benchMessagePackSerialization-opcache

  • 🟩 execution_time [-9.992µs; -7.968µs] or [-8.676%; -6.918%]

scenario:PDOBench/benchPDOOverhead

  • 🟥 execution_time [+9.190µs; +12.835µs] or [+3.560%; +4.972%]

scenario:PDOBench/benchPDOOverheadWithDBM

  • 🟥 execution_time [+10.454µs; +13.611µs] or [+4.078%; +5.310%]

Apply the single-state allocation and free forwarding path to PHP 8.3 and older. Preserve the required ZendMM prepare/restore calls and neighboring custom allocator support while removing the intermediate function pointers and wrappers. On ZTS this also avoids repeated TSRM lookups.

Across six balanced 60-second runs per binary on PHP 8.3 ZTS, mean allocation throughput increased from 28,079,883/s to 32,661,234/s (+16.32%) and median throughput increased by 16.65%. A full 60-second native sample reduced known forwarding-path self time from 16.31% to 12.30% (-24.6% relative).

Validation: PHP 8.3 ZTS cargo test (22 passed); allocation sampling-distance, memory-peak, and GC PHPTs passed; PHP 8.5 release build and cargo test (22 passed).

https://datadoghq.atlassian.net/browse/PROF-15506
The free-handler selection test retains allocation callbacks that now call _zend_mm_free directly, but Rust unit-test binaries are not loaded by PHP. Provide a test-only stub with release and debug PHP signatures so both ZendMM API implementations link.

Verified with cargo test on PHP 8.5 NTS and PHP 8.3 ZTS; both pass 22 tests.
Forward reallocations from one ZendMMState read instead of loading a function pointer, making an indirect Rust call, and reloading state. Preserve neighboring custom allocator support and the PHP 8.3-and-older heap prepare/restore calls.

Remove the realloc forwarding field, intermediate wrappers, and initialization panic callback from both ZendMM implementations.

Validation: PHP 8.5 NTS and PHP 8.3 ZTS cargo test (22 passed each); five allocation PHPTs passed on both builds.

https://datadoghq.atlassian.net/browse/PROF-15506
@realFlowControl realFlowControl added Overhead Relates to latency, CPU, or memory overhead AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos labels Jul 28, 2026
Base automatically changed from florian/allocation-profiler-fast-execute-data to master July 28, 2026 08:06
On PHP 8.3 and older, select the direct ZendMM or neighboring custom allocator callback once during rinit. The normal allocation callback no longer loads or branches on prev_custom_mm_alloc for every allocation; the custom allocator callback remains as a documented cold compatibility path.

Across six balanced 60-second PHP 8.3 ZTS runs per binary, mean throughput increased from 31,167,496/s to 32,646,366/s (+4.74%) and median throughput increased by 4.56%. Native samples reduced alloc_prof_malloc self time from 6.13% to 3.54% (-42.3% relative).

The equivalent PHP 8.5 NTS experiment measured within system noise (+0.79% mean / +0.39% median) and was not retained.

Validation: PHP 8.5 NTS cargo test (22 passed), PHP 8.3 ZTS and PHP 7.3 ZTS cargo check, and five PHP 8.3 allocation PHPTs.

https://datadoghq.atlassian.net/browse/PROF-15506
Apply the same RINIT-selected allocation callback used for PHP 8.3 and older to PHP 8.4 and newer. The normal callback has no previous-allocator load or branch, while a separate cold callback preserves neighboring custom allocator support.

The PHP 8.5 NTS benchmark was inconclusive but non-negative: a clean six-run retry measured +0.40% mean and +0.69% median with about 1.8% run variance. Keep the implementation for symmetry across ZendMM APIs rather than as a claimed performance win.

Validation: PHP 8.5 NTS cargo test (22 passed) and profiler-release build.

https://datadoghq.atlassian.net/browse/PROF-15506
@morrisonlevi
morrisonlevi marked this pull request as ready for review July 28, 2026 19:52
@morrisonlevi
morrisonlevi requested a review from a team as a code owner July 28, 2026 19:52
@morrisonlevi
morrisonlevi merged commit 24c4207 into master Jul 28, 2026
2150 of 2158 checks passed
@morrisonlevi
morrisonlevi deleted the florian/prof-15506-faster-allocation-forwarding branch July 28, 2026 19:56

@morrisonlevi morrisonlevi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. I have reviewed the final stack and will refactor after it's all landed.

@github-actions github-actions Bot added this to the 1.24.0 milestone Jul 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI Generated Largely based on code generated by an AI or LLM. This label is the same across all dd-trace-* repos Overhead Relates to latency, CPU, or memory overhead profiling Relates to the Continuous Profiler tracing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants