Skip to content

perf(di:compile): avoid PluginList clone per area — 70% reduction in Interceptors generation phase#40613

Open
SamJUK wants to merge 1 commit intomagento:2.4-developfrom
SamJUK:perf/di-compile-pluginlist-reset
Open

perf(di:compile): avoid PluginList clone per area — 70% reduction in Interceptors generation phase#40613
SamJUK wants to merge 1 commit intomagento:2.4-developfrom
SamJUK:perf/di-compile-pluginlist-reset

Conversation

@SamJUK
Copy link
Copy Markdown
Contributor

@SamJUK SamJUK commented Mar 21, 2026

Description

InterceptionConfigurationBuilder::getPluginsList() clones $this->pluginList once per compilation area (8 areas by default: global, frontend, adminhtml, crontab, webapi_rest, webapi_soap, graphql, admin).

Each clone does two things in sequence:

  1. Deep-copies the entire PluginList object graph — _data, _inherited, _processed, _pluginInstances, _scopePriorityScheme and all merged plugin config.
  2. Immediately calls _loadScopedData(), which re-merges plugin config from scratch into the clone, overwriting everything that was just copied.

The clone allocates and copies a large object graph, then throws all the copied data away. With ~877 plugins across 8 areas this creates significant unnecessary allocation and GC pressure in the Interceptors generation phase.

Fix: add a reset() method to the PluginList subclass that clears those five properties back to their initial values. Replace clone $this->pluginList in InterceptionConfigurationBuilder with $this->pluginList->reset(), reusing the same instance across areas.

All five reset properties are ones that _loadScopedData() fully rebuilds anyway, so the result is identical to the clone approach. _scopePriorityScheme is reset to [Area::AREA_GLOBAL], matching the value set in the parent constructor and the correct starting state before _loadScopedData() runs.

This is safe because areas are processed sequentially within a single request — there is no shared mutable state across concurrent callers.

Related Pull Requests

Fixed Issues (if relevant)

Manual testing scenarios

Timing comparison

# Baseline on 2.4-develop
git checkout 2.4-develop
rm -rf generated/
time php bin/magento setup:di:compile --no-ansi 2>&1 | tee /tmp/compile-baseline.txt

# This branch
git checkout perf/di-compile-pluginlist-reset
rm -rf generated/
time php bin/magento setup:di:compile --no-ansi 2>&1 | tee /tmp/compile-patched.txt

# Compare the Interceptors generation phase specifically
grep -i "interceptor" /tmp/compile-baseline.txt
grep -i "interceptor" /tmp/compile-patched.txt

On a project with ~400 modules expect the Interceptors phase to drop from roughly 14–15s down to 3–5s, and total compile time to improve by around 35–40%.

Output correctness

# Keep generated/ from the branch run above, then compare against 2.4-develop output
cp -r generated/ /tmp/generated_patched
git checkout 2.4-develop && rm -rf generated/
php bin/magento setup:di:compile --no-ansi
diff -r /tmp/generated_patched/ generated/   # should produce no output

Benchmarks on real-world projects

Project Interceptors before Interceptors after Saved
~470 modules, 877 plugins 8.8s 2.2s 6.6s / 75%
~390 modules (Store1) 15.5s 3.4s 12.1s / 78%
~390 modules (Store2) 14.1s 4.3s 9.8s / 69%

Questions or comments

Happy to add integration-level coverage for the InterceptionConfigurationBuilder flow if that would help reviewers. The existing unit test already covers the reset() method and the call site change.

Contribution checklist

  • Pull request has a meaningful description of its purpose
  • All commits are accompanied by meaningful commit messages
  • All new or changed code is covered with unit/integration tests (if applicable)
  • README.md files for modified modules are updated and included in the pull request if any predefined sections require an update — no README changes needed for this fix
  • All automated tests passed successfully (all builds are green)

@m2-assistant
Copy link
Copy Markdown

m2-assistant bot commented Mar 21, 2026

Hi @SamJUK. Thank you for your contribution!
Here are some useful tips on how you can test your changes using Magento test environment.
❗ Automated tests can be triggered manually with an appropriate comment:

  • @magento run all tests - run or re-run all required tests against the PR changes
  • @magento run <test-build(s)> - run or re-run specific test build(s)
    For example: @magento run Unit Tests

<test-build(s)> is a comma-separated list of build names.

Allowed build names are:
  1. Database Compare
  2. Functional Tests CE
  3. Functional Tests EE
  4. Functional Tests B2B
  5. Integration Tests
  6. Magento Health Index
  7. Sample Data Tests CE
  8. Sample Data Tests EE
  9. Sample Data Tests B2B
  10. Static Tests
  11. Unit Tests
  12. WebAPI Tests
  13. Semantic Version Checker

You can find more information about the builds here
ℹ️ Run only required test builds during development. Run all test builds before sending your pull request for review.


For more details, review the Code Contributions documentation.
Join Magento Community Engineering Slack and ask your questions in #github channel.

@ct-prd-pr-scan
Copy link
Copy Markdown

The security team has been informed about this pull request due to the presence of risky security keywords. For security vulnerability reports, please visit Adobe's vulnerability disclosure program on HackerOne or email psirt@adobe.com.

@SamJUK SamJUK marked this pull request as ready for review March 21, 2026 17:57
…igurationBuilder

setup:di:compile clones $pluginList once per compilation area (8 areas by default).
The clone deep-copies the entire PluginList object only to immediately discard all
its state in _loadScopedData(). The allocation and copy are pure waste.

Add a reset() method to PluginList that clears _inherited and _processed while
keeping the underlying _data array intact, then use reset() instead of clone in
InterceptionConfigurationBuilder::getPluginsList().

Benchmark results (measured across three real-world installs):

| Project     | Interceptors before | Interceptors after | Saving |
|-------------|--------------------|--------------------|--------|
| sandbox     | 8.8s               | 2.2s               | 74%    |
| ma-griggs   | 14.9s              | 4.5s               | 70%    |
| elesi       | 14.1s              | 4.3s               | 70%    |

| Project     | Total before | Total after | Total saving |
|-------------|-------------|-------------|--------------|
| sandbox     | 27.2s        | 18.5s       | 32%          |
| ma-griggs   | 41.3s        | 24.5s       | 41%          |
| elesi       | 39.1s        | 24.2s       | 38%          |

This is the single highest-impact change in the series.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SamJUK SamJUK force-pushed the perf/di-compile-pluginlist-reset branch from 45f33e6 to 9ea77eb Compare March 21, 2026 22:57
@SamJUK
Copy link
Copy Markdown
Contributor Author

SamJUK commented Mar 21, 2026

@magento run all tests

@engcom-Hotel engcom-Hotel added the Priority: P2 A defect with this priority could have functionality issues which are not to expectations. label Mar 24, 2026
@github-project-automation github-project-automation bot moved this to Pending Review in Pull Requests Dashboard Mar 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority: P2 A defect with this priority could have functionality issues which are not to expectations. Progress: pending review

Projects

Status: Pending Review

Development

Successfully merging this pull request may close these issues.

2 participants