Created
June 29, 2019 21:21
-
-
Save MichalStrehovsky/3ded26f244e0d60d2129f1a578874e07 to your computer and use it in GitHub Desktop.
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
using System; | |
using System.Collections.Concurrent; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Globalization; | |
using System.IO; | |
using System.Linq; | |
using System.Threading; | |
using System.Threading.Tasks; | |
internal static class Program | |
{ | |
private static readonly int ProcessorCount = Environment.ProcessorCount; | |
private static void Main(string[] args) | |
{ | |
int ai = 1; | |
int threadCount; | |
if (args[ai].EndsWith("PcT")) | |
{ | |
double pcMultiplier; | |
if (!double.TryParse(args[ai].Substring(0, args[ai].Length - "PcT".Length), out pcMultiplier)) | |
return; | |
threadCount = Math.Max(1, (int)Math.Round(ProcessorCount * pcMultiplier)); | |
} | |
else if (args[ai].EndsWith("T")) | |
{ | |
if (!int.TryParse(args[ai].Substring(0, args[ai].Length - "T".Length), out threadCount)) | |
return; | |
} | |
else | |
return; | |
++ai; | |
switch (args[0]) | |
{ | |
case "MresWaitDrainRate": | |
MresWaitDrainRate(threadCount); | |
break; | |
case "MresWaitLatency": | |
MresWaitLatency(threadCount); | |
break; | |
case "SemaphoreSlimWaitDrainRate": | |
SemaphoreSlimWaitDrainRate(threadCount); | |
break; | |
case "SemaphoreSlimLatency": | |
SemaphoreSlimLatency(threadCount); | |
break; | |
case "SemaphoreSlimThroughput": | |
SemaphoreSlimThroughput(threadCount); | |
break; | |
case "SpinLockLatency": | |
SpinLockLatency(threadCount); | |
break; | |
case "SpinLockThroughput": | |
SpinLockThroughput(threadCount); | |
break; | |
case "ConcurrentBagThroughput": | |
ConcurrentBagThroughput(threadCount); | |
break; | |
case "ConcurrentBagFairness": | |
ConcurrentBagFairness(threadCount); | |
break; | |
case "ConcurrentQueueThroughput": | |
ConcurrentQueueThroughput(threadCount); | |
break; | |
case "ConcurrentQueueFairness": | |
ConcurrentQueueFairness(threadCount); | |
break; | |
case "ConcurrentStackThroughput": | |
ConcurrentStackThroughput(threadCount); | |
break; | |
case "ConcurrentStackFairness": | |
ConcurrentStackFairness(threadCount); | |
break; | |
case "BarrierSyncRate": | |
BarrierSyncRate(threadCount); | |
break; | |
case "CountdownEventSyncRate": | |
CountdownEventSyncRate(threadCount); | |
break; | |
case "ThreadPoolSustainedWorkThroughput": | |
ThreadPoolSustainedWorkThroughput(threadCount); | |
break; | |
case "ThreadPoolBurstWorkThroughput": | |
{ | |
if (ai >= args.Length || !args[ai].EndsWith("PcWi")) | |
return; | |
double workItemCountPcMultiplier; | |
if (!double.TryParse(args[ai].Substring(0, args[ai].Length - "PcWi".Length), out workItemCountPcMultiplier)) | |
return; | |
int maxWorkItemCount = Math.Max(1, (int)Math.Round(ProcessorCount * workItemCountPcMultiplier)); | |
ThreadPoolBurstWorkThroughput(threadCount, maxWorkItemCount); | |
break; | |
} | |
case "TaskSustainedWorkThroughput": | |
TaskSustainedWorkThroughput(threadCount); | |
break; | |
case "TaskBurstWorkThroughput": | |
{ | |
if (ai >= args.Length || !args[ai].EndsWith("PcWi")) | |
return; | |
double workItemCountPcMultiplier; | |
if (!double.TryParse(args[ai].Substring(0, args[ai].Length - "PcWi".Length), out workItemCountPcMultiplier)) | |
return; | |
int maxWorkItemCount = Math.Max(1, (int)Math.Round(ProcessorCount * workItemCountPcMultiplier)); | |
TaskBurstWorkThroughput(threadCount, maxWorkItemCount); | |
break; | |
} | |
case "MonitorEnterExitThroughput_ThinLock": | |
MonitorEnterExitThroughput(1, false, false); | |
break; | |
case "MonitorEnterExitThroughput_AwareLock": | |
MonitorEnterExitThroughput(1, false, true); | |
break; | |
case "MonitorReliableEnterExitThroughput_ThinLock": | |
MonitorReliableEnterExitThroughput(1, false, false); | |
break; | |
case "MonitorReliableEnterExitThroughput_AwareLock": | |
MonitorReliableEnterExitThroughput(1, false, true); | |
break; | |
case "MonitorTryEnterExitWhenUnlockedThroughput_ThinLock": | |
MonitorTryEnterExitWhenUnlockedThroughput_ThinLock(1); | |
break; | |
case "MonitorTryEnterExitWhenUnlockedThroughput_AwareLock": | |
MonitorTryEnterExitWhenUnlockedThroughput_AwareLock(1); | |
break; | |
case "MonitorTryEnterWhenLockedThroughput_ThinLock": | |
MonitorTryEnterWhenLockedThroughput_ThinLock(1); | |
break; | |
case "MonitorTryEnterWhenLockedThroughput_AwareLock": | |
MonitorTryEnterWhenLockedThroughput_AwareLock(1); | |
break; | |
case "MonitorReliableEnterExitLatency": | |
MonitorReliableEnterExitLatency(threadCount); | |
break; | |
case "MonitorEnterExitThroughput": | |
MonitorEnterExitThroughput(threadCount, true, false); | |
break; | |
case "MonitorReliableEnterExitThroughput": | |
MonitorReliableEnterExitThroughput(threadCount, true, false); | |
break; | |
case "MonitorTryEnterExitThroughput": | |
MonitorTryEnterExitThroughput(threadCount, true, false); | |
break; | |
case "MonitorReliableEnterExit1PcTOtherWorkThroughput": | |
MonitorReliableEnterExit1PcTOtherWorkThroughput(threadCount); | |
break; | |
case "MonitorReliableEnterExitRoundRobinThroughput": | |
MonitorReliableEnterExitRoundRobinThroughput(threadCount); | |
break; | |
case "MonitorReliableEnterExitFairness": | |
MonitorReliableEnterExitFairness(threadCount); | |
break; | |
case "BufferMemoryCopyThroughput": | |
BufferMemoryCopyThroughput(threadCount); | |
break; | |
} | |
} | |
[ThreadStatic] | |
private static Random t_rng; | |
private static void MresWaitDrainRate(int threadCount) | |
{ | |
var startTest = new ManualResetEvent(false); | |
var allWaitersWoken0 = new ManualResetEvent(false); | |
var allWaitersWoken1 = new ManualResetEvent(false); | |
int waiterWokenCount = 0; | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var e = new ManualResetEventSlim(false); | |
ThreadStart waitThreadStart = () => | |
{ | |
var localThreadCount = threadCount; | |
var localThreadOperationCounts = threadOperationCounts; | |
startTest.WaitOne(); | |
var allWaitersWoken = allWaitersWoken0; | |
while (true) | |
{ | |
e.Wait(); | |
if (Interlocked.Increment(ref waiterWokenCount) == localThreadCount) | |
{ | |
++localThreadOperationCounts[16]; | |
waiterWokenCount = 0; | |
e.Reset(); | |
(allWaitersWoken == allWaitersWoken0 ? allWaitersWoken1 : allWaitersWoken0).Reset(); | |
allWaitersWoken.Set(); | |
} | |
else | |
allWaitersWoken.WaitOne(); | |
allWaitersWoken = allWaitersWoken == allWaitersWoken0 ? allWaitersWoken1 : allWaitersWoken0; | |
} | |
}; | |
var waitThreads = new Thread[threadCount]; | |
for (int i = 0; i < waitThreads.Length; ++i) | |
{ | |
var t = new Thread(waitThreadStart); | |
t.IsBackground = true; | |
t.Start(); | |
waitThreads[i] = t; | |
} | |
var signalThread = new Thread(() => | |
{ | |
var rng = new Random(0); | |
var allWaitersWoken = allWaitersWoken0; | |
startTest.WaitOne(); | |
while (true) | |
{ | |
Delay(RandomShortDelay(rng)); | |
e.Set(); | |
allWaitersWoken.WaitOne(); | |
allWaitersWoken = allWaitersWoken == allWaitersWoken0 ? allWaitersWoken1 : allWaitersWoken0; | |
} | |
}); | |
signalThread.IsBackground = true; | |
signalThread.Start(); | |
Run(startTest, threadOperationCounts, hasOneResult: true); | |
} | |
private static void MresWaitLatency(int threadCount) | |
{ | |
var startTest = new ManualResetEvent(false); | |
var continueWaitThreads = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var e = new ManualResetEventSlim(false); | |
ParameterizedThreadStart waitThreadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadOperationCounts = threadOperationCounts; | |
startTest.WaitOne(); | |
while (true) | |
{ | |
e.Wait(); | |
++localThreadOperationCounts[threadIndex]; | |
continueWaitThreads.WaitOne(); | |
} | |
}; | |
var waitThreads = new Thread[threadCount]; | |
for (int i = 0; i < waitThreads.Length; ++i) | |
{ | |
var t = new Thread(waitThreadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
waitThreads[i] = t; | |
} | |
var signalThread = new Thread(() => | |
{ | |
var rng = new Random(0); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
Delay(RandomShortDelay(rng)); | |
continueWaitThreads.Reset(); | |
e.Set(); | |
e.Reset(); | |
continueWaitThreads.Set(); | |
} | |
}); | |
signalThread.IsBackground = true; | |
signalThread.Start(); | |
Run(startTest, threadOperationCounts); | |
} | |
private static void SemaphoreSlimWaitDrainRate(int threadCount) | |
{ | |
var startTest = new ManualResetEvent(false); | |
var allWaitersWoken0 = new ManualResetEvent(false); | |
var allWaitersWoken1 = new ManualResetEvent(false); | |
int waiterWokenCount = 0; | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var ss = new SemaphoreSlim(0); | |
ThreadStart waitThreadStart = () => | |
{ | |
var localThreadCount = threadCount; | |
var localThreadOperationCounts = threadOperationCounts; | |
var allWaitersWoken = allWaitersWoken0; | |
startTest.WaitOne(); | |
while (true) | |
{ | |
ss.Wait(); | |
if (Interlocked.Increment(ref waiterWokenCount) == localThreadCount) | |
{ | |
++localThreadOperationCounts[16]; | |
waiterWokenCount = 0; | |
(allWaitersWoken == allWaitersWoken0 ? allWaitersWoken1 : allWaitersWoken0).Reset(); | |
allWaitersWoken.Set(); | |
} | |
else | |
allWaitersWoken.WaitOne(); | |
allWaitersWoken = allWaitersWoken == allWaitersWoken0 ? allWaitersWoken1 : allWaitersWoken0; | |
} | |
}; | |
var waitThreads = new Thread[threadCount]; | |
for (int i = 0; i < waitThreads.Length; ++i) | |
{ | |
var t = new Thread(waitThreadStart); | |
t.IsBackground = true; | |
t.Start(); | |
waitThreads[i] = t; | |
} | |
var signalThread = new Thread(() => | |
{ | |
var localThreadCount = threadCount; | |
var rng = new Random(0); | |
var allWaitersWoken = allWaitersWoken0; | |
startTest.WaitOne(); | |
while (true) | |
{ | |
Delay(RandomShortDelay(rng)); | |
ss.Release(localThreadCount); | |
allWaitersWoken.WaitOne(); | |
allWaitersWoken = allWaitersWoken == allWaitersWoken0 ? allWaitersWoken1 : allWaitersWoken0; | |
} | |
}); | |
signalThread.IsBackground = true; | |
signalThread.Start(); | |
Run(startTest, threadOperationCounts, hasOneResult: true); | |
} | |
private static void SemaphoreSlimLatency(int threadCount) | |
{ | |
var startTest = new ManualResetEvent(false); | |
int previousLockThreadId = -1; | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var ss = new SemaphoreSlim(1); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
int threadId = Environment.CurrentManagedThreadId; | |
var localThreadOperationCounts = threadOperationCounts; | |
var rng = new Random(threadIndex); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
ss.Wait(); | |
previousLockThreadId = threadId; | |
Delay(d0); | |
ss.Release(); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
while (previousLockThreadId == threadId) | |
Delay(4); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void SemaphoreSlimThroughput(int threadCount) | |
{ | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var ss = new SemaphoreSlim(1); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadOperationCounts = threadOperationCounts; | |
var rng = new Random(threadIndex); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
ss.Wait(); | |
Delay(d0); | |
ss.Release(); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void SpinLockLatency(int threadCount) | |
{ | |
var startTest = new ManualResetEvent(false); | |
int previousLockThreadId = -1; | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var m = new SpinLock(enableThreadOwnerTracking: false); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
int threadId = Environment.CurrentManagedThreadId; | |
var localThreadOperationCounts = threadOperationCounts; | |
var rng = new Random(threadIndex); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
bool lockTaken = false; | |
m.Enter(ref lockTaken); | |
previousLockThreadId = threadId; | |
Delay(d0); | |
m.Exit(); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
while (previousLockThreadId == threadId) | |
Delay(4); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void SpinLockThroughput(int threadCount) | |
{ | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var m = new SpinLock(enableThreadOwnerTracking: false); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadOperationCounts = threadOperationCounts; | |
var rng = new Random(threadIndex); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
bool lockTaken = false; | |
m.Enter(ref lockTaken); | |
Delay(d0); | |
m.Exit(); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void ConcurrentBagThroughput(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var cb = new ConcurrentBag<int>(); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
int threadId = Environment.CurrentManagedThreadId; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localCb = cb; | |
var rng = new Random(threadIndex); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
localCb.Add(threadId); | |
Delay(d0); | |
int item; | |
localCb.TryTake(out item); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void ConcurrentBagFairness(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var threadWaitDurationsUs = new double[(threadCount + 1) * 16]; | |
var cb = new ConcurrentBag<int>(); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
int threadId = Environment.CurrentManagedThreadId; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localThreadWaitDurationsUs = threadWaitDurationsUs; | |
var localCb = cb; | |
var rng = new Random(threadIndex); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
var startTicks = Clock.Ticks; | |
localCb.Add(threadId); | |
var stopTicks = Clock.Ticks; | |
++localThreadOperationCounts[threadIndex]; | |
localThreadWaitDurationsUs[threadIndex] += | |
BiasWaitDurationUsAgainstLongWaits(Clock.TicksToUs(stopTicks - startTicks)); | |
Delay(d0); | |
startTicks = Clock.Ticks; | |
int item; | |
localCb.TryTake(out item); | |
stopTicks = Clock.Ticks; | |
++localThreadOperationCounts[threadIndex]; | |
localThreadWaitDurationsUs[threadIndex] += | |
BiasWaitDurationUsAgainstLongWaits(Clock.TicksToUs(stopTicks - startTicks)); | |
Delay(d1); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
RunFairness(startTest, threadOperationCounts, threadWaitDurationsUs); | |
} | |
private static void ConcurrentQueueThroughput(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var cq = new ConcurrentQueue<int>(); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
int threadId = Environment.CurrentManagedThreadId; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localCq = cq; | |
var rng = new Random(threadIndex); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
localCq.Enqueue(threadId); | |
Delay(d0); | |
int item; | |
localCq.TryDequeue(out item); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void ConcurrentQueueFairness(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var threadWaitDurationsUs = new double[(threadCount + 1) * 16]; | |
var cq = new ConcurrentQueue<int>(); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
int threadId = Environment.CurrentManagedThreadId; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localThreadWaitDurationsUs = threadWaitDurationsUs; | |
var localCq = cq; | |
var rng = new Random(threadIndex); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
var startTicks = Clock.Ticks; | |
localCq.Enqueue(threadId); | |
var stopTicks = Clock.Ticks; | |
++localThreadOperationCounts[threadIndex]; | |
localThreadWaitDurationsUs[threadIndex] += | |
BiasWaitDurationUsAgainstLongWaits(Clock.TicksToUs(stopTicks - startTicks)); | |
Delay(d0); | |
startTicks = Clock.Ticks; | |
int item; | |
localCq.TryDequeue(out item); | |
stopTicks = Clock.Ticks; | |
++localThreadOperationCounts[threadIndex]; | |
localThreadWaitDurationsUs[threadIndex] += | |
BiasWaitDurationUsAgainstLongWaits(Clock.TicksToUs(stopTicks - startTicks)); | |
Delay(d1); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
RunFairness(startTest, threadOperationCounts, threadWaitDurationsUs); | |
} | |
private static void ConcurrentStackThroughput(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var cs = new ConcurrentStack<int>(); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
int threadId = Environment.CurrentManagedThreadId; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localCs = cs; | |
var rng = new Random(threadIndex); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
localCs.Push(threadId); | |
Delay(d0); | |
int item; | |
localCs.TryPop(out item); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void ConcurrentStackFairness(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var threadWaitDurationsUs = new double[(threadCount + 1) * 16]; | |
var cs = new ConcurrentStack<int>(); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
int threadId = Environment.CurrentManagedThreadId; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localThreadWaitDurationsUs = threadWaitDurationsUs; | |
var localCs = cs; | |
var rng = new Random(threadIndex); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
var startTicks = Clock.Ticks; | |
localCs.Push(threadId); | |
var stopTicks = Clock.Ticks; | |
++localThreadOperationCounts[threadIndex]; | |
localThreadWaitDurationsUs[threadIndex] += | |
BiasWaitDurationUsAgainstLongWaits(Clock.TicksToUs(stopTicks - startTicks)); | |
Delay(d0); | |
startTicks = Clock.Ticks; | |
int item; | |
localCs.TryPop(out item); | |
stopTicks = Clock.Ticks; | |
++localThreadOperationCounts[threadIndex]; | |
localThreadWaitDurationsUs[threadIndex] += | |
BiasWaitDurationUsAgainstLongWaits(Clock.TicksToUs(stopTicks - startTicks)); | |
Delay(d1); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
RunFairness(startTest, threadOperationCounts, threadWaitDurationsUs); | |
} | |
private static void BarrierSyncRate(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var delayComplete0 = new ManualResetEvent(false); | |
var delayComplete1 = new ManualResetEvent(false); | |
int syncThreadCount = threadCount; | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var b = new Barrier(threadCount); | |
var rng = new Random(0); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadCount = threadCount; | |
var localDelayComplete0 = delayComplete0; | |
var localDelayComplete1 = delayComplete1; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localB = b; | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
localB.SignalAndWait(); | |
if (Interlocked.Decrement(ref syncThreadCount) == 0) | |
{ | |
syncThreadCount = localThreadCount; | |
localDelayComplete1.Reset(); | |
localDelayComplete0.Set(); | |
} | |
else | |
localDelayComplete0.WaitOne(); | |
if (Interlocked.Decrement(ref syncThreadCount) == 0) | |
{ | |
++localThreadOperationCounts[16]; | |
Delay(RandomShortDelay(rng)); | |
syncThreadCount = localThreadCount; | |
localDelayComplete0.Reset(); | |
localDelayComplete1.Set(); | |
} | |
else | |
localDelayComplete1.WaitOne(); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void CountdownEventSyncRate(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var delayComplete0 = new ManualResetEvent(false); | |
var delayComplete1 = new ManualResetEvent(false); | |
int syncThreadCount = threadCount; | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var cde = new CountdownEvent(threadCount * 2); | |
var rng = new Random(0); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadCount = threadCount; | |
var localDelayComplete0 = delayComplete0; | |
var localDelayComplete1 = delayComplete1; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localCde = cde; | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
localCde.Signal(2); | |
if (Interlocked.Decrement(ref syncThreadCount) == 0) | |
{ | |
syncThreadCount = localThreadCount; | |
localDelayComplete1.Reset(); | |
localDelayComplete0.Set(); | |
} | |
else | |
localDelayComplete0.WaitOne(); | |
if (Interlocked.Decrement(ref syncThreadCount) == 0) | |
{ | |
++localThreadOperationCounts[16]; | |
Delay(RandomShortDelay(rng)); | |
syncThreadCount = localThreadCount; | |
localCde.Reset(localThreadCount * 2); | |
localDelayComplete0.Reset(); | |
localDelayComplete1.Set(); | |
} | |
else | |
localDelayComplete1.WaitOne(); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void ThreadPoolSustainedWorkThroughput(int threadCount) | |
{ | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
ThreadPool.SetMinThreads(threadCount, threadCount); | |
ThreadPool.SetMaxThreads(threadCount, threadCount); | |
WaitCallback workItemStart = null; | |
workItemStart = data => | |
{ | |
ThreadPool.QueueUserWorkItem(workItemStart); | |
var rng = t_rng; | |
if (rng == null) | |
t_rng = rng = new Random(0); | |
Delay(RandomShortDelay(rng)); | |
Interlocked.Increment(ref threadOperationCounts[16]); | |
}; | |
var producerThread = new Thread(() => | |
{ | |
var localWorkItemStart = workItemStart; | |
startTest.WaitOne(); | |
int initialWorkItemCount = ProcessorCount + threadCount * 4; | |
for (int i = 0; i < initialWorkItemCount; ++i) | |
ThreadPool.QueueUserWorkItem(localWorkItemStart); | |
}); | |
producerThread.IsBackground = true; | |
producerThread.Start(); | |
Run(startTest, threadOperationCounts, hasOneResult: true); | |
} | |
private static void ThreadPoolBurstWorkThroughput(int threadCount, int maxWorkItemCount) | |
{ | |
var startTest = new ManualResetEvent(false); | |
var workComplete = new AutoResetEvent(false); | |
int workItemCountToQueue = 0; | |
int workItemCountToComplete = 0; | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
ThreadPool.SetMinThreads(threadCount, threadCount); | |
ThreadPool.SetMaxThreads(threadCount, threadCount); | |
WaitCallback workItemStart = null; | |
workItemStart = data => | |
{ | |
int n = Interlocked.Add(ref workItemCountToQueue, -2); | |
if (n >= -1) | |
{ | |
var localWorkItemStart = workItemStart; | |
ThreadPool.QueueUserWorkItem(localWorkItemStart); | |
if (n >= 0) | |
ThreadPool.QueueUserWorkItem(localWorkItemStart); | |
} | |
var rng = t_rng; | |
if (rng == null) | |
t_rng = rng = new Random(0); | |
Delay(RandomShortDelay(rng)); | |
Interlocked.Increment(ref threadOperationCounts[16]); | |
if (Interlocked.Decrement(ref workItemCountToComplete) == 0) | |
workComplete.Set(); | |
}; | |
var producerThread = new Thread(() => | |
{ | |
var localMaxWorkItemCount = maxWorkItemCount; | |
var localWorkItemStart = workItemStart; | |
startTest.WaitOne(); | |
while (true) | |
{ | |
workItemCountToQueue = localMaxWorkItemCount - 1; | |
workItemCountToComplete = localMaxWorkItemCount; | |
ThreadPool.QueueUserWorkItem(localWorkItemStart); | |
workComplete.WaitOne(); | |
} | |
}); | |
producerThread.IsBackground = true; | |
producerThread.Start(); | |
Run(startTest, threadOperationCounts, hasOneResult: true); | |
} | |
private static void TaskSustainedWorkThroughput(int threadCount) | |
{ | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
ThreadPool.SetMinThreads(threadCount, threadCount); | |
ThreadPool.SetMaxThreads(threadCount, threadCount); | |
Action workItemStart = null; | |
workItemStart = () => | |
{ | |
Task.Run(workItemStart); | |
var rng = t_rng; | |
if (rng == null) | |
t_rng = rng = new Random(0); | |
Delay(RandomShortDelay(rng)); | |
Interlocked.Increment(ref threadOperationCounts[16]); | |
}; | |
Action initialWorkItemStart = () => | |
{ | |
var localWorkItemStart = workItemStart; | |
for (int i = 0; i < 4; ++i) | |
Task.Run(localWorkItemStart); | |
}; | |
var producerThread = new Thread(() => | |
{ | |
var localThreadCount = threadCount; | |
var localInitialWorkItemStart = initialWorkItemStart; | |
startTest.WaitOne(); | |
int initialWorkItemCount = ProcessorCount + threadCount; | |
for (int i = 0; i < initialWorkItemCount; ++i) | |
Task.Run(localInitialWorkItemStart); | |
}); | |
producerThread.IsBackground = true; | |
producerThread.Start(); | |
Run(startTest, threadOperationCounts, hasOneResult: true); | |
} | |
private static void TaskBurstWorkThroughput(int threadCount, int maxWorkItemCount) | |
{ | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
ThreadPool.SetMinThreads(threadCount, threadCount); | |
ThreadPool.SetMaxThreads(threadCount, threadCount); | |
Action<object> workItemStart = null; | |
workItemStart = async data => | |
{ | |
Task t0 = null, t1 = null; | |
int toQueue = (int)data; | |
if (toQueue > 1) | |
{ | |
var localWorkItemStart = workItemStart; | |
--toQueue; | |
t0 = new Task(localWorkItemStart, toQueue - toQueue / 2); | |
t0.Start(); | |
t1 = new Task(localWorkItemStart, toQueue / 2); | |
t1.Start(); | |
} | |
else if (toQueue != 0) | |
{ | |
t0 = new Task(workItemStart, 0); | |
t0.Start(); | |
} | |
var rng = t_rng; | |
if (rng == null) | |
t_rng = rng = new Random(0); | |
Delay(RandomShortDelay(rng)); | |
Interlocked.Increment(ref threadOperationCounts[16]); | |
if (t0 != null) | |
{ | |
await t0; | |
if (t1 != null) | |
await t1; | |
} | |
}; | |
var producerThread = new Thread(() => | |
{ | |
var localMaxWorkItemCount = maxWorkItemCount; | |
var localWorkItemStart = workItemStart; | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var t = new Task(localWorkItemStart, localMaxWorkItemCount - 1); | |
t.Start(); | |
t.Wait(); | |
} | |
}); | |
producerThread.IsBackground = true; | |
producerThread.Start(); | |
Run(startTest, threadOperationCounts, hasOneResult: true); | |
} | |
private static void MonitorReliableEnterExitLatency(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
int previousLockThreadId = -1; | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var m = new object(); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
int threadId = Environment.CurrentManagedThreadId; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localM = m; | |
var rng = new Random(threadIndex); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
lock (localM) | |
{ | |
previousLockThreadId = threadId; | |
Delay(d0); | |
} | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
while (previousLockThreadId == threadId) | |
Delay(4); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void MonitorReliableEnterExitThroughput(int threadCount, bool delay, bool convertToAwareLock) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var m = new object(); | |
if (convertToAwareLock) | |
Monitor.Enter(m); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localDelay = delay; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localM = m; | |
var rng = localDelay ? new Random(threadIndex) : null; | |
threadReady.Set(); | |
if (convertToAwareLock) | |
{ | |
Monitor.Enter(localM); | |
Monitor.Exit(localM); | |
} | |
startTest.WaitOne(); | |
if (localDelay) | |
{ | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
lock (localM) | |
Delay(d0); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
} | |
} | |
else | |
{ | |
while (true) | |
{ | |
lock (localM) | |
{ | |
} | |
++localThreadOperationCounts[threadIndex]; | |
} | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
if (convertToAwareLock) | |
{ | |
Thread.Sleep(50); | |
Monitor.Exit(m); | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void MonitorEnterExitThroughput(int threadCount, bool delay, bool convertToAwareLock) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var m = new object(); | |
if (convertToAwareLock) | |
Monitor.Enter(m); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localDelay = delay; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localM = m; | |
var rng = localDelay ? new Random(threadIndex) : null; | |
threadReady.Set(); | |
if (convertToAwareLock) | |
{ | |
Monitor.Enter(localM); | |
Monitor.Exit(localM); | |
} | |
startTest.WaitOne(); | |
if (localDelay) | |
{ | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
Monitor.Enter(localM); | |
Delay(d0); | |
Monitor.Exit(localM); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
} | |
} | |
else | |
{ | |
while (true) | |
{ | |
Monitor.Enter(localM); | |
Monitor.Exit(localM); | |
++localThreadOperationCounts[threadIndex]; | |
} | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
if (convertToAwareLock) | |
{ | |
Thread.Sleep(50); | |
Monitor.Exit(m); | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void MonitorTryEnterExitThroughput(int threadCount, bool delay, bool convertToAwareLock) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var m = new object(); | |
if (convertToAwareLock) | |
Monitor.Enter(m); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localDelay = delay; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localM = m; | |
var rng = localDelay ? new Random(threadIndex) : null; | |
threadReady.Set(); | |
if (convertToAwareLock) | |
{ | |
Monitor.Enter(localM); | |
Monitor.Exit(localM); | |
} | |
startTest.WaitOne(); | |
if (localDelay) | |
{ | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
if (!Monitor.TryEnter(localM, -1)) | |
return; | |
Delay(d0); | |
Monitor.Exit(localM); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
} | |
} | |
else | |
{ | |
while (true) | |
{ | |
if (!Monitor.TryEnter(localM, -1)) | |
return; | |
Monitor.Exit(localM); | |
++localThreadOperationCounts[threadIndex]; | |
} | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
if (convertToAwareLock) | |
{ | |
Thread.Sleep(50); | |
Monitor.Exit(m); | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void MonitorReliableEnterExit1PcTOtherWorkThroughput(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var otherWorkThreadOperationCounts = new int[(ProcessorCount + 1) * 16]; | |
var m = new object(); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localM = m; | |
var rng = new Random((int)data); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
lock (localM) | |
Delay(d0); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
ParameterizedThreadStart otherWorkThreadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localOtherWorkThreadOperationCounts = otherWorkThreadOperationCounts; | |
var rng = new Random(threadIndex); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
Delay(RandomShortDelay(rng)); | |
++localOtherWorkThreadOperationCounts[threadIndex]; | |
} | |
}; | |
var otherWorkThreads = new Thread[ProcessorCount]; | |
for (int i = 0; i < otherWorkThreads.Length; ++i) | |
{ | |
var t = new Thread(otherWorkThreadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
otherWorkThreads[i] = t; | |
} | |
RunWithOtherWork(startTest, threadOperationCounts, otherWorkThreadOperationCounts); | |
} | |
private static void MonitorReliableEnterExitRoundRobinThroughput(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var mutexes = new object[threadCount]; | |
for (int i = 0; i < mutexes.Length; ++i) | |
mutexes[i] = new object(); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localMutexes = mutexes; | |
int mutexCount = localMutexes.Length; | |
int mutexIndex = (threadIndex / 16 - 1) % mutexCount; | |
var rng = new Random(threadIndex); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
lock (localMutexes[mutexIndex]) | |
Delay(d0); | |
++localThreadOperationCounts[threadIndex]; | |
Delay(d1); | |
mutexIndex = (mutexIndex + 1) % mutexCount; | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void MonitorReliableEnterExitFairness(int threadCount) | |
{ | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var threadWaitDurationsUs = new double[(threadCount + 1) * 16]; | |
var m = new object(); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localThreadWaitDurationsUs = threadWaitDurationsUs; | |
var localM = m; | |
var rng = new Random(threadIndex); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
var d0 = RandomShortDelay(rng); | |
var d1 = RandomShortDelay(rng); | |
var startTicks = Clock.Ticks; | |
long stopTicks; | |
lock (localM) | |
{ | |
stopTicks = Clock.Ticks; | |
Delay(d0); | |
} | |
++localThreadOperationCounts[threadIndex]; | |
localThreadWaitDurationsUs[threadIndex] += | |
BiasWaitDurationUsAgainstLongWaits(Clock.TicksToUs(stopTicks - startTicks)); | |
Delay(d1); | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
RunFairness(startTest, threadOperationCounts, threadWaitDurationsUs); | |
} | |
private static void MonitorTryEnterExitWhenUnlockedThroughput_ThinLock(int threadCount) | |
{ | |
threadCount = 1; | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var m = new object(); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localM = m; | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
if (!Monitor.TryEnter(localM)) | |
return; | |
Monitor.Exit(localM); | |
++localThreadOperationCounts[threadIndex]; | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
} | |
private static void MonitorTryEnterExitWhenUnlockedThroughput_AwareLock(int threadCount) | |
{ | |
threadCount = 1; | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var m = new object(); | |
Monitor.Enter(m); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localM = m; | |
threadReady.Set(); | |
Monitor.Enter(localM); | |
Monitor.Exit(localM); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
if (!Monitor.TryEnter(localM)) | |
return; | |
Monitor.Exit(localM); | |
++localThreadOperationCounts[threadIndex]; | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Thread.Sleep(50); | |
Monitor.Exit(m); | |
Run(startTest, threadOperationCounts); | |
} | |
private static void MonitorTryEnterWhenLockedThroughput_ThinLock(int threadCount) | |
{ | |
threadCount = 1; | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var m = new object(); | |
Monitor.Enter(m); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localM = m; | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
if (Monitor.TryEnter(localM)) | |
return; | |
++localThreadOperationCounts[threadIndex]; | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts); | |
Monitor.Exit(m); | |
} | |
private static void MonitorTryEnterWhenLockedThroughput_AwareLock(int threadCount) | |
{ | |
threadCount = 1; | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
var m = new object(); | |
Monitor.Enter(m); | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadOperationCounts = threadOperationCounts; | |
var localM = m; | |
threadReady.Set(); | |
if (Monitor.TryEnter(localM, 50)) | |
return; | |
startTest.WaitOne(); | |
while (true) | |
{ | |
if (Monitor.TryEnter(localM)) | |
return; | |
++localThreadOperationCounts[threadIndex]; | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Thread.Sleep(50); | |
Run(startTest, threadOperationCounts); | |
Monitor.Exit(m); | |
} | |
private static unsafe void BufferMemoryCopyThroughput(int maxBytes) | |
{ | |
const int threadCount = 1; | |
int minBytes = maxBytes <= 8 ? 1 : maxBytes / 2 + 1; | |
var threadReady = new AutoResetEvent(false); | |
var startTest = new ManualResetEvent(false); | |
var threadOperationCounts = new int[(threadCount + 1) * 16]; | |
ParameterizedThreadStart threadStart = data => | |
{ | |
int threadIndex = (int)data; | |
var localThreadOperationCounts = threadOperationCounts; | |
var rng = new Random(0); | |
var src = stackalloc byte[maxBytes]; | |
var dst = stackalloc byte[maxBytes]; | |
for (int i = 0; i < maxBytes; ++i) | |
src[i] = (byte)rng.Next(); | |
threadReady.Set(); | |
startTest.WaitOne(); | |
while (true) | |
{ | |
Buffer.MemoryCopy(src, dst, maxBytes, rng.Next(minBytes, maxBytes + 1)); | |
++localThreadOperationCounts[threadIndex]; | |
} | |
}; | |
var threads = new Thread[threadCount]; | |
for (int i = 0; i < threads.Length; ++i) | |
{ | |
var t = new Thread(threadStart); | |
t.IsBackground = true; | |
t.Start((i + 1) * 16); | |
threadReady.WaitOne(); | |
threads[i] = t; | |
} | |
Run(startTest, threadOperationCounts, iterations: 1); | |
} | |
private static void Run( | |
ManualResetEvent startTest, | |
int[] threadOperationCounts, | |
bool hasOneResult = false, | |
int iterations = 4) | |
{ | |
var sw = new Stopwatch(); | |
int threadCount = threadOperationCounts.Length / 16 - 1; | |
var afterWarmupOperationCounts = new long[threadCount]; | |
var operationCounts = new long[threadCount]; | |
startTest.Set(); | |
// Warmup | |
Thread.Sleep(100); | |
//while (true) | |
for (int j = 0; j < iterations; ++j) | |
{ | |
for (int i = 0; i < threadCount; ++i) | |
afterWarmupOperationCounts[i] = threadOperationCounts[(i + 1) * 16]; | |
// Measure | |
sw.Restart(); | |
Thread.Sleep(500); | |
sw.Stop(); | |
for (int i = 0; i < threadCount; ++i) | |
operationCounts[i] = threadOperationCounts[(i + 1) * 16]; | |
for (int i = 0; i < threadCount; ++i) | |
operationCounts[i] -= afterWarmupOperationCounts[i]; | |
double score = operationCounts.Sum() / sw.Elapsed.TotalMilliseconds; | |
Console.WriteLine("Score: {0:0.000000}", score); | |
} | |
} | |
private static void RunWithOtherWork( | |
ManualResetEvent startTest, | |
int[] threadOperationCounts, | |
int[] otherWorkThreadOperationCounts, | |
int iterations = 4) | |
{ | |
var sw = new Stopwatch(); | |
int threadCount = threadOperationCounts.Length / 16 - 1; | |
int otherWorkThreadCount = otherWorkThreadOperationCounts.Length / 16 - 1; | |
var afterWarmupOperationCounts = new long[threadCount]; | |
var otherWorkAfterWarmupOperationCounts = new long[otherWorkThreadCount]; | |
var operationCounts = new long[threadCount]; | |
var otherWorkOperationCounts = new long[otherWorkThreadCount]; | |
var operationCountSums = new double[2]; | |
startTest.Set(); | |
// Warmup | |
Thread.Sleep(100); | |
//while (true) | |
for (int j = 0; j < iterations; ++j) | |
{ | |
for (int i = 0; i < afterWarmupOperationCounts.Length; ++i) | |
afterWarmupOperationCounts[i] = threadOperationCounts[(i + 1) * 16]; | |
for (int i = 0; i < otherWorkAfterWarmupOperationCounts.Length; ++i) | |
otherWorkAfterWarmupOperationCounts[i] = otherWorkThreadOperationCounts[(i + 1) * 16]; | |
// Measure | |
sw.Restart(); | |
Thread.Sleep(500); | |
sw.Stop(); | |
for (int i = 0; i < operationCounts.Length; ++i) | |
operationCounts[i] = threadOperationCounts[(i + 1) * 16]; | |
for (int i = 0; i < otherWorkOperationCounts.Length; ++i) | |
otherWorkOperationCounts[i] = otherWorkThreadOperationCounts[(i + 1) * 16]; | |
for (int i = 0; i < operationCounts.Length; ++i) | |
operationCounts[i] -= afterWarmupOperationCounts[i]; | |
for (int i = 0; i < otherWorkOperationCounts.Length; ++i) | |
otherWorkOperationCounts[i] -= otherWorkAfterWarmupOperationCounts[i]; | |
operationCountSums[0] = operationCounts.Sum(); | |
operationCountSums[1] = otherWorkOperationCounts.Sum(); | |
double score = operationCountSums.GeometricMean(1, otherWorkThreadCount) / sw.Elapsed.TotalMilliseconds; | |
Console.WriteLine("Score: {0:0.000000}", score); | |
} | |
} | |
private static void RunFairness( | |
ManualResetEvent startTest, | |
int[] threadOperationCounts, | |
double[] threadWaitDurationsUs, | |
int iterations = 4) | |
{ | |
var sw = new Stopwatch(); | |
int threadCount = threadWaitDurationsUs.Length / 16 - 1; | |
var afterWarmupOperationCounts = new long[threadCount]; | |
var afterWarmupWaitDurationsUs = new double[threadCount]; | |
var operationCounts = new long[threadCount]; | |
var waitDurationsUs = new double[threadCount]; | |
startTest.Set(); | |
// Warmup | |
Thread.Sleep(100); | |
//while (true) | |
for (int j = 0; j < iterations; ++j) | |
{ | |
for (int i = 0; i < threadCount; ++i) | |
afterWarmupOperationCounts[i] = threadOperationCounts[(i + 1) * 16]; | |
for (int i = 0; i < threadCount; ++i) | |
afterWarmupWaitDurationsUs[i] = threadWaitDurationsUs[(i + 1) * 16]; | |
// Measure | |
sw.Restart(); | |
Thread.Sleep(500); | |
sw.Stop(); | |
for (int i = 0; i < threadCount; ++i) | |
{ | |
int ti = (i + 1) * 16; | |
operationCounts[i] = threadOperationCounts[ti]; | |
waitDurationsUs[i] = threadWaitDurationsUs[ti]; | |
} | |
for (int i = 0; i < threadCount; ++i) | |
{ | |
operationCounts[i] -= afterWarmupOperationCounts[i]; | |
waitDurationsUs[i] -= afterWarmupWaitDurationsUs[i]; | |
} | |
double averageWaitDurationUs = Math.Sqrt(waitDurationsUs.Sum() / operationCounts.Sum()); | |
if (averageWaitDurationUs < 1) | |
averageWaitDurationUs = 1; | |
double score = 100_000 / averageWaitDurationUs; | |
Console.WriteLine($"Score: {score:0.000000}"); | |
} | |
} | |
private static double BiasWaitDurationUsAgainstLongWaits(double waitDurationUs) => | |
waitDurationUs <= 1 ? 1 : waitDurationUs * waitDurationUs; | |
internal static class Clock | |
{ | |
private static readonly long s_swFrequency = Stopwatch.Frequency; | |
private static readonly double s_swFrequencyDouble = s_swFrequency; | |
public static long Ticks => Stopwatch.GetTimestamp(); | |
public static double TicksToS(long ticks) => ticks / s_swFrequencyDouble; | |
public static double TicksToMs(long ticks) => ticks * 1000 / s_swFrequencyDouble; | |
public static double TicksToUs(long ticks) => ticks * (1000 * 1000) / s_swFrequencyDouble; | |
} | |
private static uint RandomShortDelay(Random rng) => (uint)rng.Next(4, 10); | |
private static uint RandomMediumDelay(Random rng) => (uint)rng.Next(10, 15); | |
private static uint RandomLongDelay(Random rng) => (uint)rng.Next(15, 20); | |
private static int[] s_delayValues = new int[32]; | |
private static void Delay(uint n) | |
{ | |
Interlocked.MemoryBarrier(); | |
s_delayValues[16] += (int)Fib(n); | |
} | |
private static uint Fib(uint n) | |
{ | |
if (n <= 1) | |
return n; | |
return Fib(n - 2) + Fib(n - 1); | |
} | |
public static double GeometricMean(this IEnumerable<double> values, params double[] weights) | |
{ | |
double logSum = 0, weightSum = 0; | |
int weightIndex = 0; | |
foreach (var value in values) | |
{ | |
if (weightIndex >= weights.Length) | |
throw new InvalidOperationException(); | |
var weight = weights[weightIndex]; | |
++weightIndex; | |
logSum += Math.Log(value) * weight; | |
weightSum += weight; | |
} | |
return Math.Exp(logSum / weightSum); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment