Skip to content

Commit 0f1ad6e

Browse files
ondrejmirtesclaude
andcommitted
Keep a single memoization layer for located reflections
Located reflections were cached twice - by MemoizingReflector (keyed by name) and again by MemoizingSourceLocator (keyed by identifier), pinning every reflection object graph in memory twice per worker process. The locator-level cache could not simply be dropped: created reflections capture the reflector passed to SourceLocator::locateIdentifier() and resolve parent classes, interfaces and prototypes through it, bypassing MemoizingReflector entirely - the memoizing locator was the only cache absorbing those internal lookups. MemoizingReflector now locates identifiers itself instead of wrapping DefaultReflector, so created reflections capture the memoizing reflector and route internal lookups through the same name-keyed cache. The MemoizingSourceLocator wrappers are removed along with the cache.memoizingSourceLocatorEntriesCountMax parameter and the originalBetterReflectionReflector service. The stub validator container relied on the removed locator wrapper as its only memoization, so it now overrides betterReflectionSourceLocator with the stub source locator instead of swapping reflectors. In self-analysis this reduces total worker peak memory from ~2.5 GB to ~2.1 GB with unchanged elapsed time. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017DjgMHgfvwhD4ogdTaRtLp
1 parent 550c878 commit 0f1ad6e

9 files changed

Lines changed: 67 additions & 61 deletions

‎conf/config.neon‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ parameters:
126126
nodesByStringCountMax: 256
127127
resolvedPhpDocBlockCacheCountMax: 2048
128128
nameScopeMapMemoryCacheCountMax: 512
129-
memoizingSourceLocatorEntriesCountMax: 0
130129
phpStormStubsNodesCountMax: 64
131130
reportUnmatchedIgnoredErrors: true
132131
reportIgnoresWithoutComments: false

‎conf/config.stubValidator.neon‎

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,16 @@ services:
1919
arguments:
2020
allStubFiles: %allStubFiles%
2121
php8Parser: @php8PhpParser
22-
memoizingSourceLocatorEntriesCountMax: %cache.memoizingSourceLocatorEntriesCountMax%
2322

2423
# overrides service from parsers.neon
2524
defaultAnalysisParser!:
2625
factory: @stubParser
2726

28-
# overrides service from services.neon
29-
nodeScopeResolverReflector:
30-
factory: @stubReflector
31-
32-
# overrides service from services.neon so that Reflector autowires to stubReflector
33-
originalBetterReflectionReflector!:
34-
factory: @stubReflector
27+
# overrides service from services.neon so that the reflector
28+
# (and everything above it) reflects only the stub files
29+
betterReflectionSourceLocator!:
30+
factory: @stubSourceLocator
31+
autowired: false
3532

3633
# overrides service from services.neon
3734
reflectionProvider:
@@ -52,16 +49,10 @@ services:
5249
stubBetterReflectionProvider:
5350
class: PHPStan\Reflection\BetterReflection\BetterReflectionProvider
5451
arguments:
55-
reflector: @stubReflector
52+
reflector: @betterReflectionReflector
5653
universalObjectCratesClasses: %universalObjectCratesClasses%
5754
autowired: false
5855

59-
stubReflector:
60-
class: PHPStan\BetterReflection\Reflector\DefaultReflector
61-
arguments:
62-
sourceLocator: @stubSourceLocator
63-
autowired: false
64-
6556
stubSourceLocator:
6657
class: PHPStan\BetterReflection\SourceLocator\Type\SourceLocator
6758
factory: @PHPStan\PhpDoc\StubSourceLocatorFactory::create()

‎conf/parametersSchema.neon‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ parametersSchema:
150150
nodesByStringCountMax: int(),
151151
resolvedPhpDocBlockCacheCountMax: int(),
152152
nameScopeMapMemoryCacheCountMax: int(),
153-
memoizingSourceLocatorEntriesCountMax: int(),
154153
phpStormStubsNodesCountMax: int()
155154
])
156155
reportUnmatchedIgnoredErrors: bool()

‎conf/services.neon‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,6 @@ services:
4242
autowired:
4343
- PHPStan\BetterReflection\SourceLocator\SourceStubber\ReflectionSourceStubber
4444

45-
originalBetterReflectionReflector:
46-
class: PHPStan\BetterReflection\Reflector\DefaultReflector
47-
arguments:
48-
sourceLocator: @betterReflectionSourceLocator
49-
autowired: false
50-
51-
5245
# not registered using attributes because we don't want to apply service tags automatically
5346
-
5447
class: PHPStan\Dependency\ExportedNodeVisitor

‎src/PhpDoc/StubSourceLocatorFactory.php‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
use PHPStan\BetterReflection\SourceLocator\SourceStubber\PhpStormStubsSourceStubber;
88
use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
99
use PHPStan\BetterReflection\SourceLocator\Type\Composer\Psr\Psr4Mapping;
10-
use PHPStan\BetterReflection\SourceLocator\Type\MemoizingSourceLocator;
1110
use PHPStan\BetterReflection\SourceLocator\Type\PhpInternalSourceLocator;
1211
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
1312
use PHPStan\Reflection\BetterReflection\SourceLocator\OptimizedPsrAutoloaderLocatorFactory;
@@ -26,7 +25,6 @@ public function __construct(
2625
private OptimizedSingleFileSourceLocatorRepository $optimizedSingleFileSourceLocatorRepository,
2726
private OptimizedPsrAutoloaderLocatorFactory $optimizedPsrAutoloaderLocatorFactory,
2827
private array $allStubFiles,
29-
private int $memoizingSourceLocatorEntriesCountMax,
3028
)
3129
{
3230
}
@@ -52,10 +50,9 @@ public function create(): SourceLocator
5250

5351
$locators[] = new PhpInternalSourceLocator($astPhp8Locator, $this->phpStormStubsSourceStubber);
5452

55-
return new MemoizingSourceLocator(
56-
new AggregateSourceLocator($locators),
57-
$this->memoizingSourceLocatorEntriesCountMax === 0 ? null : $this->memoizingSourceLocatorEntriesCountMax,
58-
);
53+
// no MemoizingSourceLocator here - located reflections are already memoized
54+
// by MemoizingReflector, a second cache layer would only pin them in memory twice
55+
return new AggregateSourceLocator($locators);
5956
}
6057

6158
}

‎src/Reflection/BetterReflection/BetterReflectionSourceLocatorFactory.php‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
1111
use PHPStan\BetterReflection\SourceLocator\Type\Composer\Psr\Psr4Mapping;
1212
use PHPStan\BetterReflection\SourceLocator\Type\EvaledCodeSourceLocator;
13-
use PHPStan\BetterReflection\SourceLocator\Type\MemoizingSourceLocator;
1413
use PHPStan\BetterReflection\SourceLocator\Type\PhpInternalSourceLocator;
1514
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
1615
use PHPStan\DependencyInjection\AutowiredParameter;
@@ -75,8 +74,6 @@ public function __construct(
7574
private bool $playgroundMode, // makes all PHPStan classes in the PHAR discoverable with PSR-4
7675
#[AutowiredParameter]
7776
private ?string $singleReflectionFile,
78-
#[AutowiredParameter(ref: '%cache.memoizingSourceLocatorEntriesCountMax%')]
79-
private int $memoizingSourceLocatorEntriesCountMax,
8077
)
8178
{
8279
}
@@ -175,10 +172,9 @@ public function create(): SourceLocator
175172
return new AggregateSourceLocator($locators);
176173
};
177174

178-
return new MemoizingSourceLocator(
179-
new LazySourceLocator($initializer),
180-
$this->memoizingSourceLocatorEntriesCountMax === 0 ? null : $this->memoizingSourceLocatorEntriesCountMax,
181-
);
175+
// no MemoizingSourceLocator here - located reflections are already memoized
176+
// by MemoizingReflector, a second cache layer would only pin them in memory twice
177+
return new LazySourceLocator($initializer);
182178
}
183179

184180
}

‎src/Reflection/BetterReflection/Reflector/MemoizingReflector.php‎

Lines changed: 52 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
use PHPStan\BetterReflection\Reflection\ReflectionFunction;
1111
use PHPStan\BetterReflection\Reflector\Exception\IdentifierNotFound;
1212
use PHPStan\BetterReflection\Reflector\Reflector;
13+
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
1314
use PHPStan\DependencyInjection\AutowiredParameter;
1415
use PHPStan\DependencyInjection\AutowiredService;
16+
use PHPStan\ShouldNotHappenException;
1517
use function array_key_exists;
1618
use function strtolower;
1719

@@ -29,8 +31,8 @@ final class MemoizingReflector implements Reflector
2931
private array $functionReflections = [];
3032

3133
public function __construct(
32-
#[AutowiredParameter(ref: '@originalBetterReflectionReflector')]
33-
private Reflector $reflector,
34+
#[AutowiredParameter(ref: '@betterReflectionSourceLocator')]
35+
private SourceLocator $sourceLocator,
3436
)
3537
{
3638
}
@@ -51,13 +53,22 @@ public function reflectClass(string $className): ReflectionClass
5153
return $classReflection;
5254
}
5355

54-
try {
55-
return $this->classReflections[$lowerClassName] = $this->reflector->reflectClass($className);
56-
} catch (IdentifierNotFound $e) {
56+
// located directly, without a DefaultReflector - created reflections capture
57+
// the reflector passed here and resolve e.g. parent classes and interfaces
58+
// through it, so passing $this routes those lookups through this cache too
59+
$identifier = new Identifier($className, new IdentifierType(IdentifierType::IDENTIFIER_CLASS));
60+
$classReflection = $this->sourceLocator->locateIdentifier($this, $identifier);
61+
if ($classReflection === null) {
5762
$this->classReflections[$className] = null;
5863

59-
throw $e;
64+
throw IdentifierNotFound::fromIdentifier($identifier);
6065
}
66+
67+
if (!$classReflection instanceof ReflectionClass) {
68+
throw new ShouldNotHappenException();
69+
}
70+
71+
return $this->classReflections[$lowerClassName] = $classReflection;
6172
}
6273

6374
#[Override]
@@ -72,13 +83,19 @@ public function reflectConstant(string $constantName): ReflectionConstant
7283
return $constantReflection;
7384
}
7485

75-
try {
76-
return $this->constantReflections[$constantName] = $this->reflector->reflectConstant($constantName);
77-
} catch (IdentifierNotFound $e) {
86+
$identifier = new Identifier($constantName, new IdentifierType(IdentifierType::IDENTIFIER_CONSTANT));
87+
$constantReflection = $this->sourceLocator->locateIdentifier($this, $identifier);
88+
if ($constantReflection === null) {
7889
$this->constantReflections[$constantName] = null;
7990

80-
throw $e;
91+
throw IdentifierNotFound::fromIdentifier($identifier);
92+
}
93+
94+
if (!$constantReflection instanceof ReflectionConstant) {
95+
throw new ShouldNotHappenException();
8196
}
97+
98+
return $this->constantReflections[$constantName] = $constantReflection;
8299
}
83100

84101
#[Override]
@@ -94,31 +111,49 @@ public function reflectFunction(string $functionName): ReflectionFunction
94111
return $functionReflection;
95112
}
96113

97-
try {
98-
return $this->functionReflections[$lowerFunctionName] = $this->reflector->reflectFunction($functionName);
99-
} catch (IdentifierNotFound $e) {
114+
$identifier = new Identifier($functionName, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION));
115+
$functionReflection = $this->sourceLocator->locateIdentifier($this, $identifier);
116+
if ($functionReflection === null) {
100117
$this->functionReflections[$lowerFunctionName] = null;
101118

102-
throw $e;
119+
throw IdentifierNotFound::fromIdentifier($identifier);
120+
}
121+
122+
if (!$functionReflection instanceof ReflectionFunction) {
123+
throw new ShouldNotHappenException();
103124
}
125+
126+
return $this->functionReflections[$lowerFunctionName] = $functionReflection;
104127
}
105128

129+
/**
130+
* @return list<ReflectionClass>
131+
*/
106132
#[Override]
107133
public function reflectAllClasses(): iterable
108134
{
109-
return $this->reflector->reflectAllClasses();
135+
/** @var list<ReflectionClass> */
136+
return $this->sourceLocator->locateIdentifiersByType($this, new IdentifierType(IdentifierType::IDENTIFIER_CLASS));
110137
}
111138

139+
/**
140+
* @return list<ReflectionFunction>
141+
*/
112142
#[Override]
113143
public function reflectAllFunctions(): iterable
114144
{
115-
return $this->reflector->reflectAllFunctions();
145+
/** @var list<ReflectionFunction> */
146+
return $this->sourceLocator->locateIdentifiersByType($this, new IdentifierType(IdentifierType::IDENTIFIER_FUNCTION));
116147
}
117148

149+
/**
150+
* @return list<ReflectionConstant>
151+
*/
118152
#[Override]
119153
public function reflectAllConstants(): iterable
120154
{
121-
return $this->reflector->reflectAllConstants();
155+
/** @var list<ReflectionConstant> */
156+
return $this->sourceLocator->locateIdentifiersByType($this, new IdentifierType(IdentifierType::IDENTIFIER_CONSTANT));
122157
}
123158

124159
}

‎src/Testing/TestCase.neon‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ services:
99
php8Parser: @php8PhpParser
1010
fileExtensions: %fileExtensions%
1111
excludePaths: %excludePaths%
12-
memoizingSourceLocatorEntriesCountMax: %cache.memoizingSourceLocatorEntriesCountMax%
1312

1413
# overrides service from parsers.neon
1514
currentPhpVersionSimpleParser!:

‎src/Testing/TestCaseSourceLocatorFactory.php‎

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
use PHPStan\BetterReflection\SourceLocator\SourceStubber\ReflectionSourceStubber;
1010
use PHPStan\BetterReflection\SourceLocator\Type\AggregateSourceLocator;
1111
use PHPStan\BetterReflection\SourceLocator\Type\EvaledCodeSourceLocator;
12-
use PHPStan\BetterReflection\SourceLocator\Type\MemoizingSourceLocator;
1312
use PHPStan\BetterReflection\SourceLocator\Type\PhpInternalSourceLocator;
1413
use PHPStan\BetterReflection\SourceLocator\Type\SourceLocator;
1514
use PHPStan\Php\PhpVersion;
@@ -47,7 +46,6 @@ public function __construct(
4746
private PhpVersion $phpVersion,
4847
private array $fileExtensions,
4948
private ?array $excludePaths,
50-
private int $memoizingSourceLocatorEntriesCountMax,
5149
)
5250
{
5351
}
@@ -98,10 +96,9 @@ public function create(): SourceLocator
9896
$locators[] = new PhpVersionBlacklistSourceLocator(new PhpInternalSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber);
9997
$locators[] = new PhpVersionBlacklistSourceLocator(new EvaledCodeSourceLocator($astLocator, $this->reflectionSourceStubber), $this->phpstormStubsSourceStubber);
10098

101-
return new MemoizingSourceLocator(
102-
new AggregateSourceLocator($locators),
103-
$this->memoizingSourceLocatorEntriesCountMax === 0 ? null : $this->memoizingSourceLocatorEntriesCountMax,
104-
);
99+
// no MemoizingSourceLocator here - located reflections are already memoized
100+
// by MemoizingReflector, a second cache layer would only pin them in memory twice
101+
return new AggregateSourceLocator($locators);
105102
}
106103

107104
}

0 commit comments

Comments
 (0)