Last active
June 23, 2018 13:56
-
-
Save alx9r/de92c472060a574dbb6633e000dde3e7 to your computer and use it in GitHub Desktop.
Demonstration of parallel loading of a module into PowerShell runspaces while performing CPU-bound execution using the module.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# All this C# is to synthesize a signal that corresponds to when a runspace is | |
# ready to be used. | |
# see also PowerShell/PowerShell#7034 | |
Add-Type ' | |
using System.Management.Automation; | |
using System.Management.Automation.Runspaces; | |
using System.Threading.Tasks; | |
using System.Collections.Concurrent; | |
using System.Linq; | |
using System; | |
namespace ns | |
{ | |
public class RunspaceStateTransition : IEquatable<RunspaceStateTransition> | |
{ | |
public Nullable<RunspaceAvailability> Availability { get; private set; } | |
public Nullable<RunspaceState> State { get; private set; } | |
public RunspaceStateTransition | |
( | |
Nullable<RunspaceAvailability> availability, | |
Nullable<RunspaceState> state | |
) | |
{ | |
Availability = availability; | |
State = state; | |
} | |
public bool Equals(RunspaceStateTransition other) | |
{ | |
if (other==null) return false; | |
return Availability == other.Availability && | |
State == other.State; | |
} | |
} | |
public static class OpenRunspaceWaiterTask | |
{ | |
public static Task Create(Runspace runspace) | |
{ | |
var tcs = new TaskCompletionSource<object>(); | |
System.EventHandler<RunspaceAvailabilityEventArgs> availabilityHandler = null; | |
System.EventHandler<RunspaceStateEventArgs> stateHandler = null; | |
var log = new ConcurrentQueue<RunspaceStateTransition>(); | |
availabilityHandler = (s,e) => { | |
log.Enqueue( new RunspaceStateTransition(e.RunspaceAvailability,null)); | |
if ( RunspaceIsOpen(runspace.InitialSessionState,log.ToArray()) ) | |
{ | |
runspace.AvailabilityChanged -= availabilityHandler; | |
runspace.StateChanged -= stateHandler; | |
tcs.SetResult(null); | |
} | |
}; | |
stateHandler = (s,e) => { | |
log.Enqueue( new RunspaceStateTransition(null,e.RunspaceStateInfo.State)); | |
if ( RunspaceIsOpen(runspace.InitialSessionState,log.ToArray()) ) | |
{ | |
runspace.AvailabilityChanged -= availabilityHandler; | |
runspace.StateChanged -= stateHandler; | |
tcs.SetResult(null); | |
} | |
}; | |
runspace.AvailabilityChanged += availabilityHandler; | |
runspace.StateChanged += stateHandler; | |
return tcs.Task; | |
} | |
public static bool RunspaceIsOpen( | |
InitialSessionState initialSessionState, | |
RunspaceStateTransition[] log | |
) | |
{ | |
RunspaceStateTransition[] expected = null; | |
if ( initialSessionState == null || | |
initialSessionState.Modules.Count == 0 ) | |
{ | |
expected = new[] { | |
new RunspaceStateTransition(null,RunspaceState.Opening), | |
new RunspaceStateTransition(RunspaceAvailability.Available,null), | |
new RunspaceStateTransition(null,RunspaceState.Opened ) | |
}; | |
} | |
else | |
{ | |
expected = new [] { | |
new RunspaceStateTransition(null,RunspaceState.Opening), | |
new RunspaceStateTransition(RunspaceAvailability.Available,null), | |
new RunspaceStateTransition(null,RunspaceState.Opened ), | |
new RunspaceStateTransition(RunspaceAvailability.Busy,null), | |
new RunspaceStateTransition(RunspaceAvailability.Available,null) | |
}; | |
} | |
return log.SequenceEqual(expected); | |
} | |
} | |
} | |
' -ReferencedAssemblies @( | |
'System.Threading.Tasks' | |
'System.Management.Automation' | |
'System.Collections.Concurrent' | |
'System.Linq' | |
) | |
$p = [System.Environment]::ProcessorCount | |
"ProcessorCount: $p" | |
# import Pester into this SessionState for use later | |
Import-Module Pester | |
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew() | |
# create one runspace per processor | |
$initialSessionState = [initialsessionstate]::CreateDefault() | |
$initialSessionState.ImportPSModule('Pester') | |
$runspace = 1..$p | % { [runspacefactory]::CreateRunspace($initialSessionState) } | |
# create an awaiter task for each processor so we get signaled when importing completes | |
$awaiter = $runspace | % { [ns.OpenRunspaceWaiterTask]::Create($_) } | |
"$($stopwatch.ElapsedMilliseconds)ms : OpenAsync()" | |
# start module importing for all runspaces | |
$runspace.OpenAsync() | |
# Do Pester work while Pester is being imported into the other runspaces. | |
# Measure the Pester throughput. | |
$t_iteratingWhileImporting = Measure-Command { | |
$i=0 | |
while ( $awaiter | ? {-not $_.IsCompleted} ) | |
{ | |
foreach ( $x in 1..100 ) | |
{ | |
$i++ | |
1,2,3,4 | Should -be 1,2,3,4 | |
} | |
} | |
} | |
"$($stopwatch.ElapsedMilliseconds)ms : importing done" | |
"throughput while importing: $([int]($i/$t_iteratingWhileImporting.TotalSeconds)) iterations/s" | |
# Do Pester work for the same amount of time, except this time without any module loading | |
# happening at the same time. | |
# Measure the Pester throughput. | |
$endTime = $stopwatch.ElapsedMilliseconds * 2 | |
$t_iteratingSolo = Measure-Command { | |
$i=0 | |
while ( $stopwatch.ElapsedMilliseconds -lt $endTime ) | |
{ | |
foreach ( $x in 1..100 ) | |
{ | |
$i++ | |
1,2,3,4 | Should -be 1,2,3,4 | |
} | |
} | |
} | |
"throughput while solo: $([int]($i/$t_iteratingSolo.TotalSeconds)) iterations/s" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment