[Issue] Speed up setup:di:compile by removing repeated di.xml parsing
This issue is automatically created based on existing pull request: magento/magento2#41243: Speed up setup:di:compile by removing repeated di.xml parsing
Description (*)
Reading a di.xml scope re-parses and DOM-merges every file that contributes to it — 254 files and about 12k nodes for global on a stock install, roughly 0.6s. During one setup:di:compile that read happens five times for global alone, from four independent call sites that never share a result: the object manager bootstrap, the interception configuration builder, the interception cache, and the area configuration reader.
ObjectManager\Config\Reader\Dom now memoizes its parsed result per scope, normalising the scope exactly as Config\Reader\Filesystem::read() does so read() and read($defaultScope) share an entry.
The cache is per instance on purpose. A result depends on the reader's file resolver, merge rules, schema and validation state, so two differently configured readers must never see each other's results, and a cached unvalidated read must never satisfy a reader that asked for validation.
app/etc/di.xml wires this reader into the configuration loader, the interception config and the plugin list, all of which outlive a single scope read — so the parsed scopes would otherwise be held for the lifetime of the process. The reader implements ResetAfterRequestInterface to drop them, for application server mode and other long-running processes.
Measurements
Clean install, 337 modules, cold generated/, best of three, idle machine:
| before | 13.63s |
| after | 8.89s |
Generated output is byte-identical — every metadata file and every interceptor, verified by checksum.
Related Pull Requests
The parallel-compilation half of the original change has been split into a follow-up so this one can be reviewed on its own. It is on qoliber/di-compile-parallel and adds worker processes for area configuration and interceptor generation (8.89s → 6.95s), which raises questions this PR does not — inherited connections across fork(), worker budgets under cgroup quotas, and failure reporting from a child. Those belong in their own review.
Fixed Issues (if relevant)
None linked; found by profiling.
Manual testing scenarios (*)
rm -rf generated/code generated/metadata && time bin/magento setup:di:compilefind generated -name '*.php' | sort | xargs md5sum > /tmp/before.txt- Apply this branch, repeat, writing
/tmp/after.txt. diff /tmp/before.txt /tmp/after.txt— expect no differences, and a faster compile.bin/magento cache:flush, then load a storefront page and the admin to confirm the compiled config is sound.
Questions or comments
- The memoization is what makes the compile faster, and retaining the parsed scopes is inseparable from it;
_resetState()caps that for long-running processes rather than removing the trade. - While profiling this I noticed
DiCompileCommand::configureObjectManager()injectsexcludePatterns— containing the absolute install path — intoClassesScanner, and that argument is serialized into all seven compiled area configs. It makes the compiled output non-reproducible across build paths. Out of scope here, but happy to raise it separately.
Thanks to the Mage-OS reviewers on mage-os/mageos-magento2#338, whose review of the original combined branch prompted this split.
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 README.md predefined sections require an update
- All automated tests passed successfully (all builds are green)
Source: magento/magento2