Skip to content

Commit 79a31bb

Browse files
authored
Use LruCache for the other three least-recently-used caches (#6240)
1 parent 31547e8 commit 79a31bb

4 files changed

Lines changed: 39 additions & 62 deletions

File tree

src/Parser/CachedParser.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ final class CachedParser implements Parser
5959
public function __construct(
6060
private Parser $originalParser,
6161
private int $cachedNodesByStringCountMax,
62-
private int $cachedSourceBytesMax = self::CACHED_SOURCE_BYTES_DEFAULT_LIMIT,
62+
int $cachedSourceBytesMax = self::CACHED_SOURCE_BYTES_DEFAULT_LIMIT,
6363
)
6464
{
6565
$this->cachedNodesByString = new LruCache(
6666
$this->cachedNodesByStringCountMax,
67-
$this->cachedSourceBytesMax,
67+
$cachedSourceBytesMax,
6868
self::SIZE_EVICTION_FLOOR_LIMIT,
6969
);
7070
$this->cachedSourceByFile = new LruCache(maxWeight: self::MEMOIZED_SOURCE_BYTES_LIMIT);

src/Reflection/Php/PhpClassReflectionExtension.php

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use PHPStan\BetterReflection\Reflection\Adapter\ReflectionProperty;
1717
use PHPStan\DependencyInjection\AutowiredParameter;
1818
use PHPStan\DependencyInjection\AutowiredService;
19+
use PHPStan\Internal\LruCache;
1920
use PHPStan\Parser\Parser;
2021
use PHPStan\Php\PhpVersion;
2122
use PHPStan\PhpDoc\PhpDocInheritanceResolver;
@@ -62,7 +63,6 @@
6263
use PHPStan\Type\TypehintHelper;
6364
use PHPStan\Type\UnionType;
6465
use function array_key_exists;
65-
use function array_key_first;
6666
use function array_keys;
6767
use function array_map;
6868
use function array_slice;
@@ -77,8 +77,8 @@
7777
final class PhpClassReflectionExtension
7878
{
7979

80-
/** @var array<string, true> shared LRU over the member cache keys below; first entry = least recently used */
81-
private array $memberCacheOrder = [];
80+
/** @var LruCache<true> shared LRU over the member cache keys below */
81+
private LruCache $memberCacheOrder;
8282

8383
/** @var PhpPropertyReflection[][] */
8484
private array $propertiesIncludingAnnotations = [];
@@ -118,9 +118,10 @@ public function __construct(
118118
private bool $inferPrivatePropertyTypeFromConstructor,
119119
private PhpVersion $phpVersion,
120120
#[AutowiredParameter(ref: '%cache.memberCacheKeysMax%')]
121-
private int $memberCacheKeysMax,
121+
int $memberCacheKeysMax,
122122
)
123123
{
124+
$this->memberCacheOrder = new LruCache($memberCacheKeysMax);
124125
}
125126

126127
/**
@@ -135,25 +136,18 @@ public function __construct(
135136
*/
136137
private function touchMemberCacheKey(string $cacheKey): void
137138
{
138-
if (isset($this->memberCacheOrder[$cacheKey])) {
139-
unset($this->memberCacheOrder[$cacheKey]);
140-
$this->memberCacheOrder[$cacheKey] = true;
139+
if ($this->memberCacheOrder->get($cacheKey) !== null) {
141140
return;
142141
}
143142

144-
$this->memberCacheOrder[$cacheKey] = true;
145-
if ($this->memberCacheKeysMax === 0 || count($this->memberCacheOrder) <= $this->memberCacheKeysMax) {
146-
return;
143+
foreach ($this->memberCacheOrder->set($cacheKey, true, 0) as $evictKey) {
144+
unset(
145+
$this->methodsIncludingAnnotations[$evictKey],
146+
$this->nativeMethods[$evictKey],
147+
$this->propertiesIncludingAnnotations[$evictKey],
148+
$this->nativeProperties[$evictKey],
149+
);
147150
}
148-
149-
$evictKey = array_key_first($this->memberCacheOrder);
150-
unset(
151-
$this->memberCacheOrder[$evictKey],
152-
$this->methodsIncludingAnnotations[$evictKey],
153-
$this->nativeMethods[$evictKey],
154-
$this->propertiesIncludingAnnotations[$evictKey],
155-
$this->nativeProperties[$evictKey],
156-
);
157151
}
158152

159153
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool

src/Type/FileTypeMapper.php

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use PHPStan\File\FileContentHasher;
1616
use PHPStan\File\FileHelper;
1717
use PHPStan\Internal\ComposerHelper;
18+
use PHPStan\Internal\LruCache;
1819
use PHPStan\Parser\Parser;
1920
use PHPStan\PhpDoc\NameScopeAlreadyBeingCreatedException;
2021
use PHPStan\PhpDoc\PhpDocNodeResolver;
@@ -58,10 +59,8 @@ final class FileTypeMapper
5859
private const SKIP_NODE = 1;
5960
private const POP_TYPE_MAP_STACK = 2;
6061

61-
/** @var array<string, array{array<string, IntermediaryNameScope>}> */
62-
private array $memoryCache = [];
63-
64-
private int $memoryCacheCount = 0;
62+
/** @var LruCache<array{array<string, IntermediaryNameScope>}> */
63+
private LruCache $memoryCache;
6564

6665
/** @var array<string, true> */
6766
private array $inProcess = [];
@@ -87,9 +86,13 @@ public function __construct(
8786
#[AutowiredParameter(ref: '%cache.resolvedPhpDocBlockCacheCountMax%')]
8887
private int $resolvedPhpDocBlockCacheCountMax,
8988
#[AutowiredParameter(ref: '%cache.nameScopeMapMemoryCacheCountMax%')]
90-
private int $nameScopeMapMemoryCacheCountMax,
89+
int $nameScopeMapMemoryCacheCountMax,
9190
)
9291
{
92+
// 0 kept one entry here rather than meaning "no limit" as it does for the other bounded
93+
// caches: the eviction loop ran before the insertion, emptying the cache and then putting
94+
// a single entry back. Preserved rather than normalised - see the PR description.
95+
$this->memoryCache = new LruCache($nameScopeMapMemoryCacheCountMax === 0 ? 1 : $nameScopeMapMemoryCacheCountMax);
9396
}
9497

9598
/** @api */
@@ -336,13 +339,8 @@ public function getNameScope(
336339
*/
337340
private function getNameScopeMap(string $fileName): array
338341
{
339-
if (isset($this->memoryCache[$fileName])) {
340-
// LRU: move the freshly-accessed entry to the end so eviction drops
341-
// genuinely cold files, not hot dependencies inserted early on.
342-
$cachedEntry = $this->memoryCache[$fileName];
343-
unset($this->memoryCache[$fileName]);
344-
$this->memoryCache[$fileName] = $cachedEntry;
345-
342+
$cachedEntry = $this->memoryCache->get($fileName);
343+
if ($cachedEntry !== null) {
346344
return $cachedEntry;
347345
}
348346

@@ -364,19 +362,10 @@ private function getNameScopeMap(string $fileName): array
364362
} else {
365363
[$nameScopeMap] = $cached;
366364
}
367-
while ($this->memoryCacheCount >= $this->nameScopeMapMemoryCacheCountMax) {
368-
$oldestKey = array_key_first($this->memoryCache);
369-
if ($oldestKey === null) {
370-
break;
371-
}
372-
unset($this->memoryCache[$oldestKey]);
373-
$this->memoryCacheCount--;
374-
}
375-
376-
$this->memoryCache[$fileName] = [$nameScopeMap];
377-
$this->memoryCacheCount++;
365+
$entry = [$nameScopeMap];
366+
$this->memoryCache->set($fileName, $entry, 0);
378367

379-
return $this->memoryCache[$fileName];
368+
return $entry;
380369
}
381370

382371
/**

src/Type/UsefulTypeAliasResolver.php

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55
use PHPStan\Analyser\NameScope;
66
use PHPStan\DependencyInjection\AutowiredParameter;
77
use PHPStan\DependencyInjection\AutowiredService;
8+
use PHPStan\Internal\LruCache;
89
use PHPStan\PhpDoc\TypeNodeResolver;
910
use PHPStan\PhpDoc\TypeStringResolver;
1011
use PHPStan\Reflection\ReflectionProvider;
1112
use PHPStan\ShouldNotHappenException;
1213
use function array_key_exists;
13-
use function array_key_first;
14-
use function count;
1514
use function sprintf;
1615

1716
#[AutowiredService(as: TypeAliasResolver::class)]
@@ -21,8 +20,8 @@ final class UsefulTypeAliasResolver implements TypeAliasResolver
2120
/** @var array<string, Type> */
2221
private array $resolvedGlobalTypeAliases = [];
2322

24-
/** @var array<string, Type> LRU; first entry = least recently used */
25-
private array $resolvedLocalTypeAliases = [];
23+
/** @var LruCache<Type> */
24+
private LruCache $resolvedLocalTypeAliases;
2625

2726
/** @var array<string, true> */
2827
private array $resolvingClassTypeAliases = [];
@@ -40,9 +39,10 @@ public function __construct(
4039
private TypeNodeResolver $typeNodeResolver,
4140
private ReflectionProvider $reflectionProvider,
4241
#[AutowiredParameter(ref: '%cache.resolvedLocalTypeAliasesCountMax%')]
43-
private int $resolvedLocalTypeAliasesCountMax,
42+
int $resolvedLocalTypeAliasesCountMax,
4443
)
4544
{
45+
$this->resolvedLocalTypeAliases = new LruCache($resolvedLocalTypeAliasesCountMax);
4646
}
4747

4848
public function hasTypeAlias(string $aliasName, ?string $classNameScope): bool
@@ -84,12 +84,9 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope):
8484

8585
$aliasNameInClassScope = $className . '::' . $aliasName;
8686

87-
if (array_key_exists($aliasNameInClassScope, $this->resolvedLocalTypeAliases)) {
88-
// LRU: move to the most-recently-used position
89-
$resolvedAliasType = $this->resolvedLocalTypeAliases[$aliasNameInClassScope];
90-
unset($this->resolvedLocalTypeAliases[$aliasNameInClassScope]);
91-
92-
return $this->resolvedLocalTypeAliases[$aliasNameInClassScope] = $resolvedAliasType;
87+
$resolvedAliasType = $this->resolvedLocalTypeAliases->get($aliasNameInClassScope);
88+
if ($resolvedAliasType !== null) {
89+
return $resolvedAliasType;
9390
}
9491

9592
// prevent infinite recursion
@@ -127,12 +124,9 @@ private function resolveLocalTypeAlias(string $aliasName, NameScope $nameScope):
127124
$resolvedAliasType = new CircularTypeAliasErrorType();
128125
}
129126

130-
$this->resolvedLocalTypeAliases[$aliasNameInClassScope] = $resolvedAliasType;
131-
if ($this->resolvedLocalTypeAliasesCountMax !== 0 && count($this->resolvedLocalTypeAliases) > $this->resolvedLocalTypeAliasesCountMax) {
132-
// resolved alias types transitively pin ClassReflections and their whole
133-
// reflection trees — evict the least recently used
134-
unset($this->resolvedLocalTypeAliases[array_key_first($this->resolvedLocalTypeAliases)]);
135-
}
127+
// resolved alias types transitively pin ClassReflections and their whole
128+
// reflection trees, so the cache is bounded and evicts the least recently used
129+
$this->resolvedLocalTypeAliases->set($aliasNameInClassScope, $resolvedAliasType, 0);
136130
unset($this->inProcess[$aliasNameInClassScope]);
137131

138132
return $resolvedAliasType;

0 commit comments

Comments
 (0)