Skip to content

Commit 621b1be

Browse files
Reduce allocations for cached command lookups
1 parent 411c3d0 commit 621b1be

2 files changed

Lines changed: 56 additions & 1 deletion

File tree

‎Engine/CommandInfoCache.cs‎

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,19 @@ public CommandInfo GetCommandInfo(string commandName, CommandTypes? commandTypes
6969
{
7070
return GetCommandInfoInternal(commandName, commandTypes);
7171
}
72+
73+
if (_commandInfoCache.TryGetValue(key, out Lazy<CommandInfo> cachedCommand))
74+
{
75+
return cachedCommand.Value;
76+
}
77+
78+
return AddCommandInfo(key, commandName, commandTypes);
79+
}
80+
81+
private CommandInfo AddCommandInfo(CommandLookupKey key, string commandName, CommandTypes? commandTypes)
82+
{
83+
// Keep the closure and Lazy allocation off the cache-hit path. Concurrent misses
84+
// still share the winning Lazy so command discovery runs once for each key.
7285
// Atomically either use PowerShell to query a command info object, or fetch it from the cache
7386
return _commandInfoCache.GetOrAdd(key, new Lazy<CommandInfo>(() => GetCommandInfoInternal(commandName, commandTypes))).Value;
7487
}
@@ -146,7 +159,7 @@ public override int GetHashCode()
146159
unchecked
147160
{
148161
int hash = 17;
149-
hash = hash * 31 + Name.ToUpperInvariant().GetHashCode();
162+
hash = hash * 31 + StringComparer.OrdinalIgnoreCase.GetHashCode(Name);
150163
hash = hash * 31 + CommandTypes.GetHashCode();
151164
return hash;
152165
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
Describe 'CommandInfoCache' {
5+
BeforeEach {
6+
$cacheType = [Microsoft.Windows.PowerShell.ScriptAnalyzer.Helper].Assembly.GetType(
7+
'Microsoft.Windows.PowerShell.ScriptAnalyzer.CommandInfoCache', $true)
8+
$cache = [Activator]::CreateInstance($cacheType)
9+
}
10+
11+
AfterEach {
12+
$cache.Dispose()
13+
}
14+
15+
It 'Returns the cached command for differently cased names and equivalent command types' {
16+
$command = $cache.GetCommandInfo('Get-Item', $null, $false)
17+
$command | Should -Not -BeNullOrEmpty
18+
$cached = $cache.GetCommandInfo('gET-iTEM', [System.Management.Automation.CommandTypes]::All, $false)
19+
[object]::ReferenceEquals($command, $cached) | Should -BeTrue
20+
}
21+
22+
It 'Keeps command-type filters separate from an existing cache entry' {
23+
$cache.GetCommandInfo('Get-Item', [System.Management.Automation.CommandTypes]::Function, $false) |
24+
Should -BeNullOrEmpty
25+
$command = $cache.GetCommandInfo('Get-Item', [System.Management.Automation.CommandTypes]::Cmdlet, $false)
26+
$command.CommandType | Should -Be ([System.Management.Automation.CommandTypes]::Cmdlet)
27+
$cache.GetCommandInfo('GET-ITEM', [System.Management.Automation.CommandTypes]::Function, $false) |
28+
Should -BeNullOrEmpty
29+
}
30+
31+
It 'Uses ordinal case-insensitive lookup independently of the current culture' {
32+
$originalCulture = [System.Threading.Thread]::CurrentThread.CurrentCulture
33+
try {
34+
$command = $cache.GetCommandInfo('Get-Item', $null, $false)
35+
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]::GetCultureInfo('tr-TR')
36+
$cached = $cache.GetCommandInfo('get-item', $null, $false)
37+
[object]::ReferenceEquals($command, $cached) | Should -BeTrue
38+
} finally {
39+
[System.Threading.Thread]::CurrentThread.CurrentCulture = $originalCulture
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)