Release: build-time extension selection, and measure the Phase 2.5 acceptance test - #24421
Merged
Merged
Conversation
…ceptance test
Adds --only= / --without= to script/generate-extension-registry.php, with a dependency closure so a
selected extension never silently loses one (--only=standard,spl,dom keeps libxml automatically, and
says so). Identity is the ext/ DIRECTORY, never getExtensionName() — 20 modules report 'standard'.
Selection is at GENERATION time, not runtime, and that is forced by the architecture: the registry
emits literal `new` expressions which the AOT compiler resolves statically, so a referenced module is
compiled in regardless of any runtime filter. Dropping the cost means dropping the reference.
Then ran the acceptance test the plan names — "a script that never calls into an extension pays
nothing for it" — instead of assuming it:
registry hello-world binary cold build
all 76 extensions 17,148,584 B 6 s
6 extensions 17,148,944 B 6 s <- 360 bytes LARGER, i.e. no change
Two measurements explain it:
- nm finds ZERO curl/mongodb/snmp/ldap symbols in a hello-world binary built with the full
76-extension registry. Unused extensions are not linked in at all.
- a script that DOES call an extension grows the binary ~572 KB (bcadd), so demand-driven linking
already works.
The binary is dominated by .text at ~15 MB — runtime and compiler core, not extensions.
CONSEQUENCE, recorded in the plan: Phase 2.5 must not be justified by user binary size or user build
time, because that benefit already exists. What survives is (1) extensibility — a new extension is a
directory, not a core edit, delivered by the generated registry in #24418 — and (2) the compiler's
OWN build cost, the 6,519-file spine as a single translation unit, which is the multi-hour gen-0
problem the per-module TU split addresses.
This matters because the plan sized this phase at "37% fewer files" and implied a proportional win
for users. Measured, that win is not there to collect, and weeks aimed at it would have produced no
user-visible change.
No behaviour change: lib/ExtensionRegistry.php is regenerated identical (76 extensions,
--check ok), check-extension-dependencies ok, ExtensionRegistryOrderTest 2 tests / 231 assertions ok.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
--only=/--without=to the registry generator — and then runs the acceptance test theplan names, which changes what this phase is for.
The measurement
RELEASE-PLAN Phase 2.5 states: "a script that never calls into an extension pays nothing for it —
not in link time, not in binary size, not in startup." I regenerated the registry with 6 extensions
instead of 76 and rebuilt hello world:
The minimal build is 360 bytes larger. No saving at all.
Two further measurements explain it:
nmfinds zerocurl/mongodb/snmp/ldapsymbols in a hello-world binary built with thefull 76-extension registry — unused extensions are never linked in;
bcadd) — so demand-drivenlinking already works.
The binary is dominated by
.textat ~15 MB: runtime and compiler core, not extensions.What this means for the phase
Phase 2.5 cannot be justified by user binary size or user build time — that benefit already
exists. The plan sizes the phase at "37% fewer files", which implies a proportional user-visible
win. Measured, it is not there to collect.
What survives, and is worth doing:
the multi-hour gen-0 problem. That is the real cost centre, and it is what the per-module TU split
addresses.
I would rather report this now than spend weeks moving module boundaries for a saving that does not
exist. The plan is updated in place so the next person does not re-derive it.
The selection mechanism
--only=/--without=, with a dependency closure so a selected extension never silently losesone:
Selection is at generation time, not runtime, and that is forced by the architecture: the
registry emits literal
newexpressions that the AOT compiler resolves statically, so a referencedmodule is compiled in regardless of any runtime filter. Dropping the cost means dropping the
reference.
Identity is the
ext/directory, nevergetExtensionName()— 20 modules report'standard'(#24418).
Gate
No behaviour change:
lib/ExtensionRegistry.phpregenerates identical (76 extensions).No sweep run: this changes a generator and a doc, and the generated file is byte-identical to the
committed one.
Aside, not chased
The
bcaddprobe binary produced no output where it should print3.3. I could not judge thatagainst Zend — this image's PHP has no bcmath, so
phpandbin/vm.phpboth fatal with "Call toundefined function bcadd()" while the compiler implements its own. Flagging rather than filing,
since the corpus cannot gate a function Zend does not have here.