可能重复:
为什么每个人都说 SpinLock 更快?
这个问题是关于 SpinLock、Monitor & Interlocked。
我做了 2 个测试来测试 的性能,Monitor
这些测试让我感到困惑。SpinLock
Interlocked
我的困惑特别是关于SpinLock
真正的速度有多快。根据我的测试SpinLock
,它比Monitor
. 但是基于一些文档和文章SpinLock
应该提供性能增益。
现在我想知道在哪些情况下会SpinLock
提高性能?
您可以在下面找到有关我执行的测试的一些详细信息:
在第一次测试中,我创建了几个线程(就像我拥有的许多硬件线程一样)访问同一个共享锁对象,执行非常短的操作(或根本没有操作:这只是一个测试)。
在第二个测试中,我创建了一个元素数组,并且很少有线程随机访问该数组中的元素。每个元素都包含自己的锁定对象:System.Object
for Monitor
test,SpinLock
object for SpinLock
test,对于Interlocked.Increment
,线程使用数组元素内部的int类型的公共变量来执行Interlocked.Increment
操作。
在每个测试中,对共享区域的访问都是循环执行的。每个测试由 3 个例程组成:
- 测试自旋锁
- 测试监视器
- 测试增量.联锁
每次测试都显示SpinLock
比 慢Monitor
。所以,自从我进行了提到的测试以来,困扰我的问题是哪些场景适合于由SpinLock
发布测试代码以提供详细信息:
(这两个测试都是针对 .net 4.5 编译的)
测试 1,线程试图获得对同一个共享锁定对象的独占访问权限
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Linq;
using System.Globalization;
using System.ComponentModel;
using System.Threading;
using System.Net.Sockets;
using System.Net;
class Program
{
static int _loopsCount = 1000000;
static int _threadsCount = -1;
static ProcessPriorityClass _processPriority = ProcessPriorityClass.RealTime;
static ThreadPriority _threadPriority = ThreadPriority.Highest;
static long _testingVar = 0;
static void Main(string[] args)
{
_threadsCount = Environment.ProcessorCount;
_threadsCount = (_threadsCount == 0) ? 1 : _threadsCount;
Console.WriteLine("Cores/processors count: {0}", Environment.ProcessorCount);
Console.WriteLine("Threads count: {0}", _threadsCount);
Process.GetCurrentProcess().PriorityClass = _processPriority;
TimeSpan tsInterlocked = ExecuteInterlocked();
TimeSpan tsSpinLock = ExecuteSpinLock();
TimeSpan tsMonitor = ExecuteMonitor();
Console.WriteLine("Test with interlocked: {0} ms\r\nTest with SpinLock: {1} ms\r\nTest with Monitor: {2} ms",
tsInterlocked.TotalMilliseconds,
tsSpinLock.TotalMilliseconds,
tsMonitor.TotalMilliseconds);
Console.ReadLine();
}
static TimeSpan ExecuteInterlocked()
{
_testingVar = 0;
ManualResetEvent _startEvent = new ManualResetEvent(false);
CountdownEvent _endCountdown = new CountdownEvent(_threadsCount);
Thread[] threads = new Thread[_threadsCount];
for (int i = 0; i < threads.Length; i++)
{
threads[i] = new Thread(() =>
{
_startEvent.WaitOne();
for (int j = 0; j < _loopsCount; j++)
{
Interlocked.Increment(ref _testingVar);
}
_endCountdown.Signal();
});
threads[i].Priority = _threadPriority;
threads[i].Start();
}
Stopwatch sw = Stopwatch.StartNew();
_startEvent.Set();
_endCountdown.Wait();
return sw.Elapsed;
}
static SpinLock _spinLock = new SpinLock();
static TimeSpan ExecuteSpinLock()
{
_testingVar = 0;
ManualResetEvent _startEvent = new ManualResetEvent(false);
CountdownEvent _endCountdown = new CountdownEvent(_threadsCount);
Thread[] threads = new Thread[_threadsCount];
for (int i = 0; i < threads.Length; i++)
{
threads[i] = new Thread(() =>
{
_startEvent.WaitOne();
bool lockTaken;
for (int j = 0; j < _loopsCount; j++)
{
lockTaken = false;
try
{
_spinLock.Enter(ref lockTaken);
_testingVar++;
}
finally
{
if (lockTaken)
{
_spinLock.Exit();
}
}
}
_endCountdown.Signal();
});
threads[i].Priority = _threadPriority;
threads[i].Start();
}
Stopwatch sw = Stopwatch.StartNew();
_startEvent.Set();
_endCountdown.Wait();
return sw.Elapsed;
}
static object _locker = new object();
static TimeSpan ExecuteMonitor()
{
_testingVar = 0;
ManualResetEvent _startEvent = new ManualResetEvent(false);
CountdownEvent _endCountdown = new CountdownEvent(_threadsCount);
Thread[] threads = new Thread[_threadsCount];
for (int i = 0; i < threads.Length; i++)
{
threads[i] = new Thread(() =>
{
_startEvent.WaitOne();
bool lockTaken;
for (int j = 0; j < _loopsCount; j++)
{
lockTaken = false;
try
{
Monitor.Enter(_locker, ref lockTaken);
_testingVar++;
}
finally
{
if (lockTaken)
{
Monitor.Exit(_locker);
}
}
}
_endCountdown.Signal();
});
threads[i].Priority = _threadPriority;
threads[i].Start();
}
Stopwatch sw = Stopwatch.StartNew();
_startEvent.Set();
_endCountdown.Wait();
return sw.Elapsed;
}
}
测试2,线程试图获得对数组元素的独占访问权,这些元素是随机挑选的,即低竞争测试
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace TestConcurrency
{
class Program
{
static int _loopsCount = 10000000;
static int _threadsCount = -1;
static int _arrayCount = 1000;
static ProcessPriorityClass _processPriority = ProcessPriorityClass.RealTime;
static ThreadPriority _threadPriority = ThreadPriority.Highest;
static void Main(string[] args)
{
_threadsCount = Environment.ProcessorCount;
_threadsCount = (_threadsCount == 0) ? 1 : _threadsCount;
Console.WriteLine("Cores/processors count: {0}", Environment.ProcessorCount);
Console.WriteLine("Threads count: {0}", _threadsCount);
Process.GetCurrentProcess().PriorityClass = _processPriority;
TimeSpan tsInterlocked = ExecuteInterlocked();
TimeSpan tsSpinLock = ExecuteSpinLock();
TimeSpan tsMonitor = ExecuteMonitor();
Console.WriteLine("Test with interlocked: {0} ms\r\nTest with SpinLock: {1} ms\r\nTest with Monitor: {2} ms",
tsInterlocked.TotalMilliseconds,
tsSpinLock.TotalMilliseconds,
tsMonitor.TotalMilliseconds);
Console.ReadLine();
}
static IEnumerable<int> newList()
{
return Enumerable.Range(0, _arrayCount);
}
static TimeSpan ExecuteMonitor()
{
ManualResetEvent _startEvent = new ManualResetEvent(false);
CountdownEvent _endCountdown = new CountdownEvent(_threadsCount);
Thread[] threads = new Thread[_threadsCount];
var array = newList().Select(i => new ArrayElementForMonitor()).ToArray();
for (int i = 0; i < threads.Length; i++)
{
int localI = i;
threads[i] = new Thread(() =>
{
Random r = new Random(localI * localI * localI);
int index = 0;
_startEvent.WaitOne();
bool lockTaken;
for (int j = 0; j < _loopsCount; j++)
{
index = r.Next(0, _arrayCount);
lockTaken = false;
try
{
Monitor.Enter(array[index].Locker, ref lockTaken);
}
finally
{
if (lockTaken)
{
Monitor.Exit(array[index].Locker);
}
}
}
_endCountdown.Signal();
});
threads[i].Priority = _threadPriority;
threads[i].Start();
}
GC.Collect();
Stopwatch sw = Stopwatch.StartNew();
_startEvent.Set();
_endCountdown.Wait();
return sw.Elapsed;
}
static TimeSpan ExecuteSpinLock()
{
ManualResetEvent _startEvent = new ManualResetEvent(false);
CountdownEvent _endCountdown = new CountdownEvent(_threadsCount);
Thread[] threads = new Thread[_threadsCount];
var array = newList().Select(i => new ArrayElementForSpinLock()).ToArray();
for (int i = 0; i < threads.Length; i++)
{
int localI = i;
threads[i] = new Thread(() =>
{
Random r = new Random(localI * localI * localI);
int index = 0;
_startEvent.WaitOne();
bool lockTaken;
for (int j = 0; j < _loopsCount; j++)
{
index = r.Next(0, _arrayCount);
lockTaken = false;
try
{
array[index].Locker.Enter(ref lockTaken);
}
finally
{
if (lockTaken)
{
array[index].Locker.Exit();
}
}
}
_endCountdown.Signal();
});
threads[i].Priority = _threadPriority;
threads[i].Start();
}
GC.Collect();
Stopwatch sw = Stopwatch.StartNew();
_startEvent.Set();
_endCountdown.Wait();
return sw.Elapsed;
}
static TimeSpan ExecuteInterlocked()
{
ManualResetEvent _startEvent = new ManualResetEvent(false);
CountdownEvent _endCountdown = new CountdownEvent(_threadsCount);
Thread[] threads = new Thread[_threadsCount];
var array = newList().Select(i => new ArrayElementInterlocked()).ToArray();
for (int i = 0; i < threads.Length; i++)
{
int localI = i;
threads[i] = new Thread(() =>
{
Random r = new Random(localI * localI * localI);
int index = 0;
_startEvent.WaitOne();
for (int j = 0; j < _loopsCount; j++)
{
index = r.Next(0, _arrayCount);
Interlocked.Increment(ref array[index].Element);
}
_endCountdown.Signal();
});
threads[i].Priority = _threadPriority;
threads[i].Start();
}
GC.Collect();
Stopwatch sw = Stopwatch.StartNew();
_startEvent.Set();
_endCountdown.Wait();
return sw.Elapsed;
}
}
public class ArrayElementForMonitor
{
public object Locker = new object();
}
public class ArrayElementForSpinLock
{
public SpinLock Locker = new SpinLock();
}
public class ArrayElementInterlocked
{
public int Element;
}
}
附加测试 3. 测试在单个线程中执行。线程访问锁的最高机会。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace TestSimpleLocking
{
class Program
{
static int _loopsCount = 100000000;
static ProcessPriorityClass _processPriority = ProcessPriorityClass.RealTime;
static ThreadPriority _threadPriority = ThreadPriority.Highest;
static void Main(string[] args)
{
Process.GetCurrentProcess().PriorityClass = _processPriority;
Thread.CurrentThread.Priority = _threadPriority;
TimeSpan tsInterlocked = ExecuteInterlocked();
TimeSpan tsSpinLock = ExecuteSpinLock();
TimeSpan tsMonitor = ExecuteMonitor();
Console.WriteLine("Test with interlocked: {0} ms\r\nTest with SpinLock: {1} ms\r\nTest with Monitor: {2} ms",
tsInterlocked.TotalMilliseconds,
tsSpinLock.TotalMilliseconds,
tsMonitor.TotalMilliseconds);
Console.ReadLine();
}
static TimeSpan ExecuteMonitor()
{
object locker = new object();
int variable = 0;
Stopwatch sw = Stopwatch.StartNew();
bool lockTaken = false;
for (int i = 0; i < _loopsCount; i++)
{
lockTaken = false;
try
{
Monitor.Enter(locker, ref lockTaken);
variable++;
}
finally
{
if (lockTaken)
{
Monitor.Exit(locker);
}
}
}
sw.Stop();
Console.WriteLine(variable);
return sw.Elapsed;
}
static TimeSpan ExecuteSpinLock()
{
SpinLock spinLock = new SpinLock();
int variable = 0;
Stopwatch sw = Stopwatch.StartNew();
bool lockTaken = false;
for (int i = 0; i < _loopsCount; i++)
{
lockTaken = false;
try
{
spinLock.Enter(ref lockTaken);
variable++;
}
finally
{
if (lockTaken)
{
spinLock.Exit();
}
}
}
sw.Stop();
Console.WriteLine(variable);
return sw.Elapsed;
}
static TimeSpan ExecuteInterlocked()
{
int variable = 0;
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < _loopsCount; i++)
{
Interlocked.Increment(ref variable);
}
sw.Stop();
Console.WriteLine(variable);
return sw.Elapsed;
}
}
}
据我了解,第三次测试是最好的SpinLock
选择。完全没有争议。单线程 - 顺序执行。为什么SpinLock
还远远落后Monitor
?谁能给我指出一些可以证明我SpinLock
有用的代码(设备驱动程序开发除外)?