Skip to content

Share one NamespaceUses between the exported PHPDocs of a file - #6585

Merged
ondrejmirtes merged 1 commit into
2.3.xfrom
exported-phpdoc-node-shared-uses
Sep 25, 2026
Merged

ondrejmirtes merged 1 commit into
2.3.xfrom
exported-phpdoc-node-shared-uses

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

Follow-up to #6583.

ExportedPhpDocNode held its own copy of the file's namespace, uses and constUses. The exportedNodes in the result cache therefore grew with the number of imports times the number of documented members. This is the same problem #6583 fixed in the file cache.

  • ExportedPhpDocNode now refers to the NamespaceUses that NamespaceUsesTracker hands out. serialize() writes it once per result cache frame (one frame per file).
  • A parallel worker sends exported nodes as JSON, which repeats the uses for every PHPDoc and loses the sharing. ParallelAnalyser now decodes each file's nodes with an ExportedNodeDecoder, which returns the same NamespaceUses instance for equal values. The decoder is passed explicitly through every decode() method, so there is no static state. The main process then holds a file's imports once, and the result cache it writes stays shared.
  • The JSON shape is unchanged. ResultCacheManager::CACHE_VERSION is bumped because the serialized shape of ExportedPhpDocNode changed.

None of the ExportedNode classes are @api.

Numbers

Full self-analysis (parallel, result cache cleared), identical in two repeats:

before after
resultCache.php 54.9 MB 51.8 MB
peak RSS of the main process 461 MB 453 MB

The saving grows with imports × documented members, so the monolith in phpstan/phpstan#15304 should see a much larger difference.

Test

ExportedNodeResolverTest::testUsesAreStoredOncePerFile checks that the serialized exported nodes of a file contain each import once, both straight from the resolver and after a JSON round trip through decode(). The decoded nodes also have to be equals() to the originals. The test fails with 5 copies instead of 1 when either the resolver side or the decoder side stops sharing.

🤖 Generated with Claude Code

https://claude.ai/code/session_0182BrC263VCQTibEPTJX2UY

ExportedPhpDocNode held its own copy of the namespace, the uses and the
constUses of the file, so the exportedNodes in the result cache grew with
the number of imports times the number of documented members.

It now refers to the NamespaceUses that NamespaceUsesTracker hands out,
which is one instance until the next namespace or use statement, and
serialize() writes it once per result cache frame.

A parallel worker sends the exported nodes as JSON, which repeats the
uses for every PHPDoc. ParallelAnalyser decodes the nodes of each file
with an ExportedNodeDecoder, passed through every decode() method, that
hands out one NamespaceUses for equal values - so the main process holds
the imports of a file once as well, and the result cache stays shared.

Full self-analysis (parallel, result cache cleared), identical in two
repeats: resultCache.php 54.9 MB -> 51.8 MB, peak RSS of the main
process 461 MB -> 453 MB.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0182BrC263VCQTibEPTJX2UY
@ondrejmirtes
ondrejmirtes merged commit 9c0e0dc into 2.3.x Sep 25, 2026
522 of 527 checks passed
@ondrejmirtes
ondrejmirtes deleted the exported-phpdoc-node-shared-uses branch September 25, 2026 04:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant