//------------------------------------------------------------------------------
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// Description: $filename$
// SchedBench is a benchmark based on SingBench that measures the CPU scheduler
// performance.
//------------------------------------------------------------------------------
using System;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.Diagnostics.Contracts;
using Microsoft.Singularity.Endpoint;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity.V1.Services;
using Microsoft.Singularity.Io;
using Microsoft.Singularity.Configuration;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.Contracts;
using Microsoft.SingSharp.Reflection;
using Microsoft.Singularity.Applications;
[assembly: Transform(typeof(ApplicationResourceTransform))]
namespace Microsoft.Singularity.Applications.SchedBench
{
/// Application wrapper class and argument options
[ConsoleCategory(HelpMessage="Singularity Scheduling Benchmark Application", DefaultAction=true)]
internal sealed class Parameters
{
[InputEndpoint("data")]
public readonly TRef Stdin;
[OutputEndpoint("data")]
public readonly TRef Stdout;
[Endpoint]
public readonly TRef memoryRef;
/// When true break in debugger before running benchmark
[BoolParameter( "b", Default=false , HelpMessage="Break at start of tests.")]
internal bool breakIn;
/// When set no GC during benchmarks
[BoolParameter( "n", Default=true , HelpMessage="No GC between tests.")]
internal bool allowGC;
/// Wait for a key between benchmarks
[BoolParameter( "w", Default=false , HelpMessage="Wait for key press between tests.")]
internal bool pauseForKeys;
/// Produce XML output
[BoolParameter( "x", Default=false , HelpMessage="XML output.")]
internal bool xmlOutput;
/// Iterations per benchmark (when applicable)
[LongParameter( "i", Default=10000 , HelpMessage="Iterate tests times.")]
internal long iterations;
reflective internal Parameters();
internal int AppMain() {
return SchedBench.AppMain(this);
}
}
/// CPU Scheduler Benchmark
public class SchedBench
{
///
///
/// Display on the console and in the debugger
///
/// Message displayed
///
private static void DualWriteLine(string message)
{
Console.WriteLine(message);
DebugStub.WriteLine(message);
}
// ----------------------------------------------------------------------------------------
// Yield Tests
// ----------------------------------------------------------------------------------------
///
///
/// Thread Yield Test - Create threads that each yields a
/// predefined number of times. Report yields per tick.
///
/// Number of threads yielding
/// Number of yield operations
///
public static void DoYieldPerf(uint numThreads, uint iterations)
{
Thread[] yieldThreads = new Thread[numThreads];
// Set worker thread iteration count
workerIterations = iterations;
// Initialize sync signal before starting threads
syncStart.Reset();
// Create and start yield worker threads
for( uint i = 0; i < numThreads; i++ ) {
yieldThreads[i] = new Thread(YieldThread);
((!)yieldThreads[i]).Start();
}
// Start performance snap
PerfSnap snap = new PerfSnap();
snap.Start();
// Synchnously start threads by signalling
syncStart.Set();
// Wait for threads to complete
for( uint i = 0; i < numThreads; i++ ) {
((!)yieldThreads[i]).Join();
}
snap.Finish(iterations);
snap.Display("Yield " + numThreads.ToString());
DualWriteLine(String.Format("Yield {0}:Ticks per yield: {1,2:F}",
numThreads.ToString(),
(float)snap.ElapsedTicks /
(float)(iterations*numThreads)));
}
///
///
/// YieldThread - Yield a number of times and synchnoize with
/// main thread
///
/// Number of yield operations
///
public static void YieldThread()
{
// Wait for signal from master thread to start
syncStart.WaitOne();
for (uint i = 0; i < workerIterations; i++) {
Thread.Yield();
}
}
// ----------------------------------------------------------------------------------------
// Wait/Set Tests
// ----------------------------------------------------------------------------------------
///
///
/// Wait / Set Benchmarks - Create pairs of threads that wait and set events
///
/// Number of threads
/// Number of wait/set operations
///
public static void DoWaitPerf(uint numThreads, uint iterations)
{
// Number of ping-pong pairs
uint pairs = numThreads / 2;
// Allocate AutoResetEvents
waitPerfEvent1 = new AutoResetEvent[pairs];
waitPerfEvent2 = new AutoResetEvent[pairs];
for(uint i = 0; i < pairs; i++) {
waitPerfEvent1[i] = new AutoResetEvent(false);
waitPerfEvent2[i] = new AutoResetEvent(false);
}
// Create threads
Thread[] pingThreads = new Thread[pairs];
Thread[] pongThreads = new Thread[pairs];
// Set worker thread iteration count
workerIterations = iterations;
// Initialize sync signal before starting threads
syncStart.Reset();
syncContext.Reset();
// Create and start yield worker threads
for( uint i = 0; i < pairs; i++ ) {
// Use curWorkerThread to set the context of worker thread
curWorkerThread = i;
// Begin ping thread
pingThreads[i] = new Thread(PingThread);
((!)pingThreads[i]).Start();
// Wait for ping thread to notify that has acquired context
syncContext.WaitOne();
syncContext.Reset();
// Begin pong thread
pongThreads[i] = new Thread(PongThread);
((!)pongThreads[i]).Start();
// Wait for pong thread to notify that has acquired context
syncContext.WaitOne();
syncContext.Reset();
}
// Start performance snap
PerfSnap snap = new PerfSnap();
snap.Start();
// Synchnously start threads by signalling
syncStart.Set();
// Wait for threads to complete
for( uint i = 0; i < pairs; i++ ) {
((!)pingThreads[i]).Join();
((!)pongThreads[i]).Join();
}
snap.Finish(iterations);
snap.Display("Wait " + numThreads.ToString());
DualWriteLine(String.Format("Wait {0}:Ticks per wait: {1,2:F}",
numThreads.ToString(),
(float)snap.ElapsedTicks /
(float)(iterations*numThreads)));
}
///
///
/// Perform a ping operation in the wait handle (Set-Wait)
///
///
private static void PingThread()
{
// Acquire context and notify master thread
uint myIdx = curWorkerThread;
syncContext.Set();
// Wait for signal from master thread to start
syncStart.WaitOne();
for (uint i = 0; i < workerIterations; i++) {
// Set event (ping)
((!)waitPerfEvent1[myIdx]).Set();
// Wait event (pong)
((!)waitPerfEvent2[myIdx]).WaitOne();
}
}
///
///
/// Perform a pong operation in the wait handle (Wait-Set)
///
///
private static void PongThread()
{
// Acquire context and notify master thread
uint myIdx = curWorkerThread;
syncContext.Set();
// Wait for signal from master thread to start
syncStart.WaitOne();
for (uint i = 0; i < workerIterations; i++) {
// Wait event (pong)
((!)waitPerfEvent1[myIdx]).WaitOne();
// Set event (ping)
((!)waitPerfEvent2[myIdx]).Set();
}
}
// ----------------------------------------------------------------------------------------
// WaitAny Benchmark
// ----------------------------------------------------------------------------------------
///
///
/// WaitAny benchmark - Main thread waits for any of the slave thread to set event
/// Measures average time to receive signal.
///
/// Number of wait/set operations
///
public static void DoWaitAnyPerf(uint numThreads,uint iterations)
{
// Allocate AutoResetEvents
waitPerfEvent1 = new AutoResetEvent[numThreads];
for(uint i = 0; i < numThreads; i++) {
waitPerfEvent1[i] = new AutoResetEvent(false);
}
// Create slave threads
Thread[] setEventThread = new Thread[numThreads];
// Set worker thread iteration count
workerIterations = iterations;
// Set operation flag array for worker threads
opFlag = new bool[numThreads];
// Initialize barrier
loopBarrier = new SyncBarrier(numThreads);
// Create and start worker threads
for( uint i = 0; i < numThreads; i++ ) {
// Use curWorkerThread to set the context of worker thread
curWorkerThread = i;
// Begin SetEvent thread
setEventThread[i] = new Thread(WaitAnyThread);
((!)setEventThread[i]).Start();
// Wait for SetEvent thread to notify that has acquired context
syncContext.WaitOne();
syncContext.Reset();
}
// Start performance snap
PerfSnap snap = new PerfSnap();
// Random generator used to pick threads to set event
// Use fix seed to replicate same sequence
Random rnd = new Random(0);
// Aggregate time for operation
long opTimer = 0;
snap.Start();
// Run the benchmark iteration
for( uint i = 0; i < iterations; i++ ) {
// Number of events to be set
int nevent = 0;
// Pick random threads to set events
for( uint j = 0; j < numThreads; j++ ) {
// 50% chance to pick thread to set event
opFlag[j] = rnd.NextDouble() < 0.5 ? true : false;
if( opFlag[j] == true ) {
nevent++;
}
}
// if no events picked (very unlikely)
if( nevent == 0 ) {
// Select one thread randomly
opFlag[rnd.Next(0,(int)numThreads)] = true;
}
// Join barrier
loopBarrier.Join();
// Start timer
long begTicks = DateTime.Now.Ticks;
WaitHandle.WaitAny(waitPerfEvent1);
// Start timer
opTimer += DateTime.Now.Ticks - begTicks;
loopBarrier.Signal();
}
snap.Finish(iterations);
snap.Display("WaitAny " + numThreads.ToString());
DualWriteLine(String.Format("WaitAny {0}:Ticks per WaitAny: {1,2:F}",
numThreads.ToString(),
(float)opTimer/ (float)iterations));
}
///
///
/// Set event to contribute in WaitAny
///
///
private static void WaitAnyThread()
{
// Acquire context and notify master thread
uint myIdx = curWorkerThread;
syncContext.Set();
for (uint i = 0; i < workerIterations; i++) {
// Increase the barrier (completed worker threads) counter
loopBarrier.JoinWorker();
// If thread selected to perform set
if( opFlag[myIdx] ) {
// Set event (ping)
((!)waitPerfEvent1[myIdx]).Set();
}
// Wait for randevouz when everybody is done
loopBarrier.Wait();
}
}
// ----------------------------------------------------------------------------------------
// Context Switch Performance
// ----------------------------------------------------------------------------------------
///
///
/// Calculate the cost of context switches
///
/// Number of threads yielding
/// Number of yield operations
///
public static void DoContextSwitchPerf(uint numThreads, uint iterations)
{
Thread[] workerThreads = new Thread[numThreads];
// Set worker thread iteration count
workerIterations = iterations;
// Initialize sync signal before starting threads
syncStart.Reset();
syncContext.Reset();
// Calculate the minimum mandelbrot time - use fix point (0.0,0.0)
long mbrotTicks = MandelbrotTicks(0.0,0.0,iterations);
// Create and start worker threads
for( uint i = 0; i < numThreads; i++ ) {
// Initialize wallClock
wallClock[i] = 0;
// Use curWorkerThread to set the context of worker thread
curWorkerThread = i;
// Begin worket thread
workerThreads[i] = new Thread(ContextThread);
((!)workerThreads[i]).Start();
// Wait for ping thread to notify that has acquired context
syncContext.WaitOne();
syncContext.Reset();
}
// Start performance snap
PerfSnap snap = new PerfSnap();
snap.Start();
// Synchonously start threads by signalling
syncStart.Set();
// Wait for threads to complete
for( uint i = 0; i < numThreads; i++ ) {
((!)workerThreads[i]).Join();
}
snap.Finish(iterations);
snap.Display("Context " + numThreads.ToString());
// Calculate context overhead
float contextOverhead = 0;
for( uint i = 0; i < numThreads; i++ ) {
contextOverhead += (float)wallClock[i] / (float)iterations;
}
contextOverhead = (float)contextOverhead / numThreads - mbrotTicks;
if( contextOverhead < 0.0 ) {
// It is possible that the context overhead is not valid especially in hosted
// execution environments such as Win32. This might occur if during the
// calculation of the mbrot baseline computation (single thread) an execution
// environment process context switch causes S to stall mbrot operation
DualWriteLine(String.Format("Context {0}:Context overhead: Invalid",
numThreads.ToString()));
} else {
DualWriteLine(String.Format("Context {0}:Context overhead: {1,2:F}",
numThreads.ToString(), contextOverhead));
}
// Calculate fairness variation
long min = 0;
long max = 0;
float avg = 0.0F;
float stddev = 0.0F;
CalcTimeStats(wallClock, numThreads, iterations, out min, out max, out avg, out stddev);
DualWriteLine(String.Format("Context {0}:Operations per cycle: {1,4:F}",
numThreads.ToString(),
(float)(iterations*numThreads) / snap.ElapsedTicks));
DualWriteLine(String.Format("Context {0}:Fairness min:{1}",
numThreads.ToString(), min));
DualWriteLine(String.Format("Context {0}:Fairness max:{1}",
numThreads.ToString(), max));
DualWriteLine(String.Format("Context {0}:Fairness avg:{1,2:F}",
numThreads.ToString(), avg));
DualWriteLine(String.Format("Context {0}:Fairness stddev:{1,2:F}",
numThreads.ToString(), stddev));
}
///
///
/// Thread that calculate a mandelbrot pixels
///
/// Number of yield operations
///
public static void ContextThread()
{
// Acquire context and notify master thread
uint myIdx = curWorkerThread;
syncContext.Set();
// Wait for signal from master thread to start
syncStart.WaitOne();
// Start timer
long begTicks = DateTime.Now.Ticks;
for (uint i = 0; i < workerIterations; i++) {
// Calc point - use fix point
ComputePoint(0.0,0.0);
}
// Add to wall clock
wallClock[myIdx] = DateTime.Now.Ticks - begTicks;
}
// ----------------------------------------------------------------------------------------
// Latency Benchmark
// ----------------------------------------------------------------------------------------
///
///
/// Multiple threads a series of mbrot calculations / Sleeps. The sleep random interval.
/// The latency of the operation is measured.
///
/// Number of threads yielding
/// Number of operations
///
public static void DoLatencyPerf(uint numThreads, uint iterations)
{
Thread[] workerThreads = new Thread[numThreads];
// Set worker thread iteration count
workerIterations = iterations;
// Initialize sync signal before starting threads
syncStart.Reset();
syncContext.Reset();
// Create and start worker threads
for( uint i = 0; i < numThreads; i++ ) {
// Initialize wallClock, min and max latencies
wallClock[i] = 0;
// Use curWorkerThread to set the context of worker thread
curWorkerThread = i;
// Begin worket thread
workerThreads[i] = new Thread(LatencyThread);
((!)workerThreads[i]).Start();
// Wait for ping thread to notify that has acquired context
syncContext.WaitOne();
syncContext.Reset();
}
// Start performance snap
PerfSnap snap = new PerfSnap();
snap.Start();
// Synchonously start threads by signalling
syncStart.Set();
// Wait for threads to complete
for( uint i = 0; i < numThreads; i++ ) {
((!)workerThreads[i]).Join();
}
snap.Finish(iterations);
snap.Display("Latency " + numThreads.ToString());
// Calculate latency statistics
long min = 0;
long max = 0;
float avg = 0.0F;
float stddev = 0.0F;
CalcTimeStats(wallClock, numThreads, iterations, out min, out max, out avg, out stddev);
DualWriteLine(String.Format("Latency {0}:min:{1}",
numThreads.ToString(), min));
DualWriteLine(String.Format("Latency {0}:max:{1}",
numThreads.ToString(), max));
DualWriteLine(String.Format("Latency {0}:mean:{1,2:F}",
numThreads.ToString(), avg));
DualWriteLine(String.Format("Latency {0}:stddev:{1,2:F}",
numThreads.ToString(), stddev));
}
///
///
/// Thread that calculate a mandelbrot pixels and then sleeps random interval
///
/// Number of yield operations
///
public static void LatencyThread()
{
// Max sleep time interval
const uint MaxSleep = 5;
// Random generator used for a random interval
Random rnd = new Random(0);
// Acquire context and notify master thread
uint myIdx = curWorkerThread;
syncContext.Set();
// Wait for signal from master thread to start
syncStart.WaitOne();
for (uint i = 0; i < workerIterations; i++) {
// Start timer
long begTicks = DateTime.Now.Ticks;
// Calc point - use fix point
ComputePoint(0.0,0.0);
// Store timers
long computeTime = DateTime.Now.Ticks - begTicks;
wallClock[myIdx] += computeTime;
// Sleep for random period
Thread.Sleep(rnd.Next(0,MaxSleep));
}
}
// ----------------------------------------------------------------------------------------
// Mandelbrot computation task and helper functions
// ----------------------------------------------------------------------------------------
///
///
/// Return the time required to calculate a mandelbrot pixel
/// Run multiple times and return minimum time trying to exclude
/// context switching time.
///
/// X coordinate
/// Y coordinate
/// Number of iterations
///
private static long MandelbrotTicks(double x, double y, uint iterations)
{
long ticks = 0; // Min ticks for mandelbrot point calculation
// Calc the same pixel many times
for( uint i = 0; i < iterations; i++ ) {
//Start timer
long begTicks = DateTime.Now.Ticks;
// Disable interrupts only mandelbrot is running
bool saved = Processor.DisableLocalPreemption();
// Calc point
ComputePoint(x,y);
Processor.RestoreLocalPreemption(saved);
//Stop timer
long endTicks = DateTime.Now.Ticks;
// Save value of minimum
ticks = i == 0 || ticks > endTicks - begTicks ?
endTicks - begTicks : ticks;
}
return ticks;
}
///
///
/// Calculate a single mandelbrot point
///
/// X coordinate
/// Y coordinate
///
static private int ComputePoint(double x, double y)
{
const int maxIterations = 128; // Maximum number of iterations for mandelbrot loop
const int convergence = 4; // Convergence value
int nIterations = 0; // Result of calculation
// Temp vars for mandelbrot calculation
double x1 = 0;
double y1 = 0;
double xx;
// Mandelbrot magic
while (nIterations < maxIterations && ((x1 * x1) + (y1 * y1)) < convergence) {
nIterations++;
xx = (x1 * x1) - (y1 * y1) + x;
y1 = 2 * x1 * y1 + y;
x1 = xx;
}
return nIterations;
}
// ----------------------------------------------------------------------------------------
// Helper functions
// ----------------------------------------------------------------------------------------
///
///
/// Calculate statistics on wall clock times
///
/// Wall clock times
/// Number of worker threads (ignore other array entries)
/// Experiment iterations per thread
/// Min value (out)
/// Max value (out)
/// Mean value (out)
/// Standard deviation (out)
///
private static void CalcTimeStats(long[] wallClock,
uint numThreads,
uint iterations,
out long min,
out long max,
out float avg,
out float stddev)
{
min = 0;
max = 0;
avg = 0;
stddev = 0;
for( uint i = 0; i < numThreads; i++ ) {
// Calc time per iteration
long clockPerIter= ((!)wallClock)[i] / iterations;
// Find min / max / avg
min = i == 0 ? clockPerIter :
min > clockPerIter ? clockPerIter :
min;
max = max < clockPerIter ? clockPerIter : max;
avg += clockPerIter;
}
avg /= numThreads;
// Standard deviation
for( uint i = 0; i < numThreads; i++ ) {
long clockPerIter= ((!)wallClock)[i] / iterations;
stddev += (float)Math.Pow((float)clockPerIter - avg,2.0);
}
stddev = (float)Math.Sqrt(stddev / ((float)numThreads - 1.0) );
}
/// Write log trace profile in console
public static int GetTrace()
{
#if false
Tracing.LogEntry * lstart;
Tracing.LogEntry * llimit;
Tracing.LogEntry ** lhead;
byte * tstart;
byte * tlimit;
byte ** thead;
Tracing.GetTracingHeaders(out lstart, out llimit, out lhead,
out tstart, out tlimit, out thead);
Console.WriteLine("Log: {0:X} {1:X}, {2:X}, {3:X}",
(UIntPtr)lstart, (UIntPtr)llimit, (UIntPtr)lhead, (UIntPtr)(*lhead));
Console.WriteLine("Text: {0:X} {1:X}, {2:X}, {3:X}",
(UIntPtr)tstart, (UIntPtr)tlimit, (UIntPtr)thead, (UIntPtr)(*thead));
#endif
return 0;
}
/// Clean up after each benchmark
public static void ClearEnvironment(MemoryContract.Imp:ReadyState! imp)
{
if (allowGC) {
GC.Collect();
}
}
/// Pause between benchmarks
public static void Pause()
{
if (pauseForKeys) {
Console.WriteLine("Press any key to continue.");
Console.Read();
}
}
internal static int AppMain(Parameters! config)
{
// Command-line parsing options
int iterations = (int) config.iterations;
bool xmlOutput = config.xmlOutput;
allowGC = config.allowGC;
pauseForKeys = config.pauseForKeys;
breakIn = config.breakIn;
if( breakIn ) {
DebugStub.Break();
}
// Display CPU privilidge level and set flag
if (Processor.AtKernelPrivilege()) {
atRing3 = false;
DualWriteLine("Schedbench running at KERNEL privilege");
} else {
atRing3 = true;
DualWriteLine("Schedbench running at USER privilege");
}
// Allocate clock arrays for each worker thread
wallClock = new long[threadCountTests[threadCountTests.Length-1]];
// Connect to memory channel
MemoryContract.Imp impMem = config.memoryRef.Acquire();
if (impMem == null) {
throw new ApplicationException("Error: Unable to bind to " +
MemoryContract.ModuleName);
}
impMem.RecvReady();
// Set options for performance snap module
PerfSnap.SetOptions(atRing3, xmlOutput);
GetTrace();
Console.WriteLine("Benchmarks: entered");
try {
// Run yield benchmark
foreach( uint threads in threadCountTests ) {
ClearEnvironment(impMem);
DoYieldPerf(threads,(uint)iterations);
Pause();
}
// Run wait-set benchmark
foreach( uint threads in threadCountTests ) {
ClearEnvironment(impMem);
DoWaitPerf(threads, (uint)iterations);
Pause();
}
// Run wait-set benchmark
foreach( uint threads in threadCountTests ) {
ClearEnvironment(impMem);
DoWaitAnyPerf(threads, (uint)iterations);
Pause();
}
// Run context switch benchmark
foreach( uint threads in threadCountTests ) {
ClearEnvironment(impMem);
DoContextSwitchPerf(threads, (uint)iterations);
Pause();
}
// Run latency benchmark
foreach( uint threads in threadCountTests ) {
ClearEnvironment(impMem);
// Because latency test is slow run 1/10th of iterations
DoLatencyPerf(threads, (uint)iterations / 10 );
Pause();
}
} catch (Exception e) {
Console.WriteLine("Caught {0}", e.Message);
delete impMem;
return 1;
}
delete impMem;
return 0;
} // AppMain
///
/// Experiments are repeated with the numbers of threads specified in the array
///
private static readonly uint[] threadCountTests = {25, 50, 100 };
/// Signal for notification that worker thread acquired context
private static ManualResetEvent syncContext = new ManualResetEvent(false);
/// Signal for synchonous start of working threads
private static ManualResetEvent syncStart = new ManualResetEvent(false);
/// Signal for synchonous start of working threads
private static SyncBarrier loopBarrier;
/// Iterations of an operation for worker threads
private static uint workerIterations;
///
/// Thread id is used to communicate to worker therads its current id.
/// This is necessary because Thread.Start(object) is not supported
///
private static uint curWorkerThread;
/// Wall clock for each worker thread
private static long[] wallClock;
/// True when running in Ring 3 processor mode
private static bool atRing3;
/// Wait events used in Wait/Set benchmarks
private static AutoResetEvent[] waitPerfEvent1;
/// Wait events used in Wait/Set benchmarks
private static AutoResetEvent[] waitPerfEvent2;
///
/// When opFlag[idx] is set to true, Thread idx performs an operation (e.g. Event.Set)
///
private static bool[] opFlag;
private static bool allowGC = false;
private static bool pauseForKeys = false;
private static bool breakIn = false;
} //class SchedBench
///
/// SyncBarrier
/// A master thread runs a loop and assigns work to worker threads.
/// All of the slave threads need to synchnonize at the end of the loop
/// before proceeding to the next iteration.
///
/// Usage pattern for master thread:
/// - FixedSyncBarrier barrier = new FixedSyncBarrier(numThreads)
/// - Loop
/// barrier.Join()
/// Do work here
/// barrier.Signal()
///
/// Usage pattern for worker thread:
/// - Loop
/// barrier.JoinWorker()
/// Do work here
/// barrier.Wait()
///
/// Currently works with fixed number of workers to simplify APIs
///
public class SyncBarrier
{
/// Constructor
/// Number of worker threads participating in loop
public SyncBarrier(uint numThreads)
{
this.numThreads = numThreads;
}
// ----------------------------------------------------------------------------------------
// Master thread operations
// ----------------------------------------------------------------------------------------
/// Master thread joins barrier at the beggining of the loop
public void Join()
{
// Wait for all threads to be ready
notifyMaster.WaitOne();
// Reset ready count and end event - all worker threads are ready for next loop
this.cntJoin = 0;
this.syncEnd.Reset();
// Synchonously start threads by signalling
this.syncStart.Set();
}
// Master thread signals workers when everybody is done
public void Signal()
{
// Wait for all threads to complete
notifyMaster.WaitOne();
// Reset thread start synchonization objects so no worker gets ahead of master
this.syncStart.Reset();
// Signal workers to continue
this.syncEnd.Set();
// Reset completed counter
this.cntCompleted = 0;
}
// ----------------------------------------------------------------------------------------
// Worker thread operations
// ----------------------------------------------------------------------------------------
/// Worker thread joins barrier
public void JoinWorker()
{
// Increase the barrier join cnt (ready worker threads)
int incJoin = Interlocked.Increment(ref this.cntJoin);
// If this is the last worker notify master thread that we are done
if( incJoin == this.numThreads ) {
// Notify master thread that we are done
notifyMaster.Set();
}
// Wait for signal from master thread to proceed
this.syncStart.WaitOne();
}
/// Worker thread waits for randevouz signal
public void Wait()
{
// Increase the barrier (completed worker threads) counter
int incCompleted = Interlocked.Increment(ref this.cntCompleted);
// If this is the last worker notify master thread that we are done
if( incCompleted == this.numThreads ) {
notifyMaster.Set();
}
// Wait for signal from master thread to proceed
this.syncEnd.WaitOne();
}
/// Number of working threads
private uint numThreads;
/// Signal for synchonous start of working threads
private ManualResetEvent syncStart = new ManualResetEvent(false);
/// Signal for synchonous start of working threads
private ManualResetEvent syncEnd = new ManualResetEvent(false);
/// Signal master thread that all workers are done
private AutoResetEvent notifyMaster = new AutoResetEvent(false);
/// Number of worker threads completed the iteration
private int cntCompleted = 0;
/// Number of worker threads joined in barrier
private int cntJoin = 0;
} // SyncBarrier
} // namespace SchedBench