-
Notifications
You must be signed in to change notification settings - Fork 382
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Increase lock granularity for CommandInfo cache #1166
Increase lock granularity for CommandInfo cache #1166
Conversation
return commandInfoCache[key]; | ||
} | ||
|
||
var commandInfo = GetCommandInfoInternal(cmdletName, commandType); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look carefully, the name
variable is being used for the creation of the key
but the cmdletname
variable is passed into this function. The name
and cmdletname
variable might not always be the same but you pass only name
into the new GetCommandInfo()
function. I think you'd have to pass the CommandLookupKey as well into it instead to preserve legacy behaviour. We have seen it very hard to make any change to this method without breaking something else, hence why this method is marked as legacy. Unless you can argue that they are always the same, I'd be careful changing it (even if tests are green)
Thanks for the efforts, I see now what you mean, yes it looks more elegant to me, I didn't know one could do caching with lambda expressions this way as well (that's why I thought originally I had to use Task). I'd have preferred it if the changeset was kept smaller. Looks ok-ish but I have 1 comment about trying to preserve legacy behaviour to reduce the risk of a potential regression. |
Fair points, I'll undo the unnecessary changes |
51d5ee6
to
a1f94f5
Compare
/// <returns>Returns null if command does not exists</returns> | ||
private static CommandInfo GetCommandInfoInternal(string cmdName, CommandTypes? commandType) | ||
{ | ||
using (var ps = System.Management.Automation.PowerShell.Create()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a chance to further improve the perf by using a static RunspacePool, instead of creating/disposing a Runspace for every call to this method.
Of course, you will need to decide the size of the pool carefully based on the typical scenarios.
The code would be something like this:
staitc RunspacePool rps = RunspaceFactory.CreateRunspacePool(minRunspaces: 1, maxRunspaces: 5)
...
private static CommandInfo GetCommandInfoInternal(string cmdName, CommandTypes? commandType)
{
using (var ps = System.Management.Automation.PowerShell.Create())
{
ps.RunspacePool = rps;
ps.AddCommand("Get-Command").Invoke()
...
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I have tried something like this already in the past but could not get any significant measurable differences in terms of time. I think because all rules are run in separate threads, the rule that takes the longest is the weakest link in the chain and therefore the bottleneck.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using a RunspacePool
here is mainly for two purpose:
- Make this method run faster
Runspace.Open
is expensive.- module auto-loading and the cache populated during command discovery can be reused
RunspacePool
may cause some extent of synchronization among the threads (more threads than the number ofRunspaces
get in this method). But only one invocation will be done by each thread, so the introduced synchronization wouldn't be that bad.
- Reduce the GC pressure
- Open and dispose a
Runspace
in every thread that gets in this method would produce a lot shot living objects.
- Open and dispose a
As for how effective it is to the overall run time improvement, maybe not much -- measurement speaks the truth :) But it looks to me a low hanging fruit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, looks good to me. I am happy to close my PR #1162 in favour of this
Co-Authored-By: rjmholt <[email protected]>
* Redo command info cache changes * Fix comment typo Co-Authored-By: rjmholt <[email protected]> * Trigger CI
PR Summary
Introduces a concurrent dictionary allowing different caller threads to get
CommandInfo
objects for different commands without blocking each other.Alternative implementation of #1162.
Closes #1162
More details:
ConcurrentDictionary<K, V>.GetOrAdd()
), so very little opportunity to introduce problems laterPR Checklist
.cs
,.ps1
and.psm1
files have the correct copyright headerWIP:
to the beginning of the title and remove the prefix when the PR is ready.