This issue is automatically created based on existing pull request: #40613: perf(di:compile): avoid PluginList clone per area — 70% reduction in Interceptors generation phase
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:
- Deep-copies the entire
PluginList object graph — _data, _inherited, _processed, _pluginInstances, _scopePriorityScheme and all merged plugin config.
- 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
This issue is automatically created based on existing pull request: #40613: perf(di:compile): avoid PluginList clone per area — 70% reduction in Interceptors generation phase
Description
InterceptionConfigurationBuilder::getPluginsList()clones$this->pluginListonce per compilation area (8 areas by default:global,frontend,adminhtml,crontab,webapi_rest,webapi_soap,graphql,admin).Each clone does two things in sequence:
PluginListobject graph —_data,_inherited,_processed,_pluginInstances,_scopePrioritySchemeand all merged plugin config._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 thePluginListsubclass that clears those five properties back to their initial values. Replaceclone $this->pluginListinInterceptionConfigurationBuilderwith$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._scopePrioritySchemeis 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
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
Benchmarks on real-world projects
Questions or comments
Happy to add integration-level coverage for the
InterceptionConfigurationBuilderflow if that would help reviewers. The existing unit test already covers thereset()method and the call site change.Contribution checklist