重複の可能性:
なぜ誰もがSpinLockの方が速いと言っているのですか?
この質問は、SpinLock、Monitor、およびInterlockedに関するものです。
のパフォーマンスをテストする2つのテストを行いましたが、Monitor
これらのテストで混乱しました。SpinLock
Interlocked
SpinLock
私の混乱は、特に本当にどれだけ速いかということです。私のテストによると、SpinLock
より遅いですMonitor
。しかし、多くのドキュメントや記事に基づいて、SpinLock
パフォーマンスを向上させる必要があります。
そして今、どのシナリオSpinLock
でパフォーマンスが向上するのだろうか?
以下に、私が実行したテストの詳細を示します。
最初のテストでは、非常に短い操作を実行して同じ共有ロックオブジェクトにアクセスするスレッドをいくつか作成しました(または操作をまったく実行しませんでした。これは単なるテストです)。
2番目のテストでは、要素の配列と、この配列内の要素にランダムにアクセスするいくつかのスレッドを作成しました。各要素には独自のロックオブジェクトが含まれています。テストの場合、テストのSystem.Object
オブジェクトの場合と同様に、スレッドは配列要素内のint型のパブリック変数を使用して操作を実行します。Monitor
SpinLock
SpinLock
Interlocked.Increment
Interlocked.Increment
各テストでは、共有領域へのアクセスがループで実行されます。各テストは、次の3つのルーチンで構成されています。
- SpinLockのテスト
- テストモニター
- Increment.Interlockedのテスト
各テストは、それ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;
}
}
}
私が理解している限り、3番目のテストがSpinLock
選択の最良のケースです。競合はまったくありません。シングルスレッド-シーケンス実行。なぜSpinLock
まだはるかに遅れているのMonitor
ですか?誰かが私にSpinLock
(デバイスドライバーの開発を除いて)まったく役立つことを証明するコードを教えてもらえますか?