#41265·magento2

[Issue] Speed up setup:di:compile by removing repeated di.xml parsing

Author: m2-assistant[bot]Created Sep 15, 2026Updated Sep 17, 2026
LabelsPriority: P2Issue: ready for confirmationArea: Performance

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 (*)

  1. rm -rf generated/code generated/metadata && time bin/magento setup:di:compile find generated -name '*.php' | sort | xargs md5sum > /tmp/before.txt
  2. Apply this branch, repeat, writing /tmp/after.txt.
  3. diff /tmp/before.txt /tmp/after.txt — expect no differences, and a faster compile.
  4. 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() injects excludePatterns — containing the absolute install path — into ClassesScanner, 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)