Needs work
Project:
Drupal core
Version:
main
Component:
base system
Priority:
Normal
Category:
Task
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
5 Feb 2026 at 19:38 UTC
Updated:
7 Apr 2026 at 15:52 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #3
longwaveBaseline serialized container: 285234 bytes
After these changes: 272228 bytes
Saving: 13006 bytes (4.6%)
Comment #4
longwaveWe can also remove the default
invalidBehaviorcase to save even more:After second round of changes: 241675 bytes
Saving: 43559 bytes (15%)
Comment #5
longwaveWe can also remove the
collectionwrapper, and therawtype is unused too.After third round of changes: 206691 bytes
Saving: 78543 bytes (27.5%)
Comment #6
longwaveFinal pass at this, heavily assisted by Claude Code.
This MR optimizes the machine-readable container format by encoding service references as bare strings instead of stdClass objects. Service references are the most common argument type (~53% of all arguments across core), so this reduces serialized size, object count, and resolution overhead.
In the optimized format, a bare string in an argument list is now a service ID with
EXCEPTION_ON_INVALID_REFERENCEbehaviour (this covers 99.6% of service references). This is the fast path inresolveServicesAndParameters().Since bare strings now mean service IDs, literal string values are wrapped in a "raw" type object; these are much rarer than service references.
"Collections" are now always just plain arrays, and the container no longer needs to unwrap collection objects.
I also wrote a test that outputs various statistics and benchmark timings from a fully-loaded container with all core modules enabled; that code is attached to this comment.
Comment #7
longwaveHad an idea that might make this even smaller with no CPU overhead: map service names to integers and use the integer values for argument references.
Comment #8
longwaveLet's leave that for now, can be done in a followup
Comment #9
dries commentedI've been poking at this and want to float an idea.
The current approach makes bare strings mean "service reference" and wraps literal strings in a
{type: 'raw'}stdClass. What if we went the other way: prefix the references instead? So'@module_handler'is a service reference,'%cache_bins'is a parameter, and'hello'is just a literal string, no wrapper needed.Your approach eliminates stdClass wrappers on service references, but every literal string argument becomes a new stdClass object. So we trade one set of allocations for another. I believe the prefix approach could eliminate both.
Comment #11
dries commentedI prototyped the prefix idea from #9 with the help of Claude Code and benchmarked it against longwave's latest MR. This builds on the great work longwave already did.
First, let me illustrate the encoding difference as it helps everyone understand it. Let's take
module_handleras an example. This is a typical service with five arguments: one literal string, one parameter reference, and three service references.Drupal main
Its dumped form looks like this (596 bytes):
Five stdClass allocations per container build for one service: one collection wrapper, one parameter wrapper, and one per service reference.
Longwave's latest MR
Longwave's latest MR flattens service references to bare strings and drops the collection wrapper (321 bytes):
Two stdClass allocations remain: one wrapping the literal string
/var/www/html(because bare strings now mean "service reference"), and one for the parameter reference.Dries' alternative encoding
The prefix variant eliminates both of the remaining wrappers (216 bytes):
Zero stdClass allocations. The literal string passes through unchanged with no prefix. The parameter reference uses a
%prefix. Service references use an@prefix. Every argument stays a bare scalar, with no heap allocation per reference.Benchmark results
Size aside, the main advantage of my approach is that it's faster, which is ultimately what we're trying to optimize for. Across a full Drupal install with all core modules enabled, prefix encoding eliminates over a thousand additional stdClass allocations that longwave's MR still makes.
Using
ContainerDumpSizeTest.phpfrom #6, with all 72 core modules enabled:Comment #12
godotislateOnly took a quick look at the prefix solution in https://git.drupalcode.org/project/drupal/-/merge_requests/15349, and it seems like it accounts for escaping raw strings starting with
@and&with@@and&&, but not%? Seems like it makes sense to use the same double escaping of%%as https://symfony.com/doc/current/configuration.html#configuration-parameters?Granted these are likely edge cases.
Comment #13
dries commented@godislate: We don't use @@/&& double-prefix escaping. For all three characters (@, &, %), literal strings that would collide with prefix encoding are wrapped in a stdClass with
type => 'raw', which the resolver treats as literal values. For %, that is done by thestr_containsblock around line 380.Comment #14
godotislate@dries: Oh, OK. I was just going off the new
testPrefixedLiteralStringEscapemethod in OptimizedPhpArrayDumperTest, which has test cases for string literals starting with@and&, but not%.Comment #15
longwaveAdded some questions and comments. Posting from my phone, hoping to review/test this from a computer later this week.
Comment #16
longwaveAlternative proposal in #3583505: Use Symfony PhpDumper instead of a serialized array container structure where we drop this array-based format entirely and swap to Symfony's PhpDumper. This was previously swapped the other way in 2015, maybe we can revisit it now.