Skip to content

Instantly share code, notes, and snippets.

@alx9r
Last active June 23, 2018 13:56
Show Gist options
  • Save alx9r/de92c472060a574dbb6633e000dde3e7 to your computer and use it in GitHub Desktop.
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.
# 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