Skip to content

Commit f8e6114

Browse files
committed
Speed up cold starts of PSSA by another 35% by parallelising the expensive command lookup in AvoidAlias, which is the most expensive rule. To accomodate the higher parallel demand, increase runspace pool.
1 parent 587ac7b commit f8e6114

File tree

2 files changed

+48
-18
lines changed

2 files changed

+48
-18
lines changed

Engine/Helper.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,9 @@ internal set
115115
/// </summary>
116116
private Helper()
117117
{
118-
// There are 5 rules that use the CommandInfo cache and each rule does not request more than one concurrent command info request
119-
_runSpacePool = RunspaceFactory.CreateRunspacePool(1, 6);
118+
// There are 5 rules that use the CommandInfo cache but one rule (AvoidAlias) makes parallel queries.
119+
// Therefore 10 runspaces was a heuristic measure where no more speed improvement was seen.
120+
_runSpacePool = RunspaceFactory.CreateRunspacePool(1, 10);
120121
_runSpacePool.Open();
121122
_commandInfoCacheLazy = new Lazy<CommandInfoCache>(() => new CommandInfoCache(pssaHelperInstance: this, runspacePool: _runSpacePool));
122123
}

Rules/AvoidAlias.cs

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
using System.Globalization;
1212
using System.Linq;
1313
using System.Management.Automation;
14+
using System.Threading.Tasks;
15+
using System.Collections.Concurrent;
1416

1517
namespace Microsoft.Windows.PowerShell.ScriptAnalyzer.BuiltinRules
1618
{
@@ -95,7 +97,8 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
9597
// Finds all CommandAsts.
9698
IEnumerable<Ast> foundAsts = ast.FindAll(testAst => testAst is CommandAst, true);
9799

98-
// Iterates all CommandAsts and check the command name.
100+
// Iterates all CommandAsts and check the command name. Expensive operations are run in background tasks
101+
var tasks = new List<Task<DiagnosticRecord>>();
99102
foreach (CommandAst cmdAst in foundAsts)
100103
{
101104
// Check if the command ast should be ignored
@@ -128,24 +131,50 @@ public IEnumerable<DiagnosticRecord> AnalyzeScript(Ast ast, string fileName)
128131
suggestedCorrections: GetCorrectionExtent(cmdAst, cmdletNameIfCommandNameWasAlias));
129132
}
130133

131-
var isNativeCommand = Helper.Instance.GetCommandInfo(commandName, CommandTypes.Application | CommandTypes.ExternalScript) != null;
132-
if (!isNativeCommand)
134+
// Checking for implicit 'Get-' aliasing is done in a background task as it can be quite expensive during a cold-start
135+
tasks.Add(Task<DiagnosticRecord>.Run(() =>
133136
{
134-
var commdNameWithGetPrefix = $"Get-{commandName}";
135-
var cmdletNameIfCommandWasMissingGetPrefix = Helper.Instance.GetCommandInfo($"Get-{commandName}");
136-
if (cmdletNameIfCommandWasMissingGetPrefix != null)
137-
{
138-
yield return new DiagnosticRecord(
139-
string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingCmdletAliasesMissingGetPrefixError, commandName, commdNameWithGetPrefix),
140-
GetCommandExtent(cmdAst),
141-
GetName(),
142-
DiagnosticSeverity.Warning,
143-
fileName,
144-
commandName,
145-
suggestedCorrections: GetCorrectionExtent(cmdAst, commdNameWithGetPrefix));
146-
}
137+
return CheckForImplicitGetAliasing(commandName, cmdAst, fileName);
138+
}));
139+
}
140+
foreach(var task in tasks)
141+
{
142+
var diagnosticRecordResult = task.Result;
143+
if (diagnosticRecordResult != null)
144+
{
145+
yield return task.Result;
146+
}
147+
}
148+
}
149+
150+
/// <summary>
151+
/// If one omitts, 'Get-' for a command, PowerShell will pre-pend it if such a command exists, therefore relying on such implicit aliasing is not desirable.
152+
/// </summary>
153+
/// <param name="commandName"></param>
154+
/// <param name="commandAst"></param>
155+
/// <param name="fileName"></param>
156+
/// <returns></returns>
157+
private DiagnosticRecord CheckForImplicitGetAliasing(string commandName, CommandAst commandAst, string fileName)
158+
{
159+
var isNativeCommand = Helper.Instance.GetCommandInfo(commandName, CommandTypes.Application | CommandTypes.ExternalScript) != null;
160+
if (!isNativeCommand)
161+
{
162+
var commdNameWithGetPrefix = $"Get-{commandName}";
163+
var cmdletNameIfCommandWasMissingGetPrefix = Helper.Instance.GetCommandInfo($"Get-{commandName}");
164+
if (cmdletNameIfCommandWasMissingGetPrefix != null)
165+
{
166+
var diagnosticRecord = new DiagnosticRecord(
167+
string.Format(CultureInfo.CurrentCulture, Strings.AvoidUsingCmdletAliasesMissingGetPrefixError, commandName, commdNameWithGetPrefix),
168+
GetCommandExtent(commandAst),
169+
GetName(),
170+
DiagnosticSeverity.Warning,
171+
fileName,
172+
commandName,
173+
suggestedCorrections: GetCorrectionExtent(commandAst, commdNameWithGetPrefix));
174+
return diagnosticRecord;
147175
}
148176
}
177+
return null;
149178
}
150179

151180
/// <summary>

0 commit comments

Comments
 (0)