0

重複の可能性:
なぜ誰もがSpinLockの方が速いと言っているのですか?

この質問は、SpinLock、Monitor、およびInterlockedに関するものです。

のパフォーマンスをテストする2つのテストを行いましたが、Monitorこれらのテストで混乱しました。SpinLockInterlocked

SpinLock私の混乱は、特に本当にどれだけ速いかということです。私のテストによると、SpinLockより遅いですMonitor。しかし、多くのドキュメントや記事に基づいて、SpinLockパフォーマンスを向上させる必要があります。

そして今、どのシナリオSpinLockでパフォーマンスが向上するのだろうか?

以下に、私が実行したテストの詳細を示します。

最初のテストでは、非常に短い操作を実行して同じ共有ロックオブジェクトにアクセスするスレッドをいくつか作成しました(または操作をまったく実行しませんでした。これは単なるテストです)。

2番目のテストでは、要素の配列と、この配列内の要素にランダムにアクセスするいくつかのスレッドを作成しました。各要素には独自のロックオブジェクトが含まれています。テストの場合、テストのSystem.Objectオブジェクトの場合と同様に、スレッドは配列要素内のint型のパブリック変数を使用して操作を実行します。MonitorSpinLockSpinLockInterlocked.IncrementInterlocked.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(デバイスドライバーの開発を除いて)まったく役立つことを証明するコードを教えてもらえますか?

4

1 に答える 1

2

リソースの競合が少ない場合 (つまり、ほとんどの場合、リソースのロックが成功する場合)、SpinLock は非常に高速です。参照: Joe Duffy の本とブログhttp://www.bluebytesoftware.com/blog/

各テストでは、共有領域へのアクセスがループで実行されます

_could_は、競合が高いことを意味します。(ところで、完全なコード例を投稿できますか?必要な「推測」を減らすのに役立ちます)。したがって、SpinLock がスピンしてから待機する可能性が高く、直接待機するモニターよりも悪化します。

編集:あなたの閉じた関連する質問の詳細を読んだ後:私はハンス・パッサンの答えに完全に同意します:

したがって、基本的な要件は、ロックが非常に短時間保持されることです。これは、あなたの場合に当てはまります。そして、ロックを取得できる合理的な可能性があること。あなたの場合はそうではありませんが、ロックは24以上のスレッドによって激しく争われています。

SpinLock をやみくもに使用することは、少なくともその設計の背後にある原則を測定したり理解したりせずに、時期尚早の最適化のケースであり、実際にはより遅い、または正しくないコードにすぐに実行される可能性があります。いくつかの同期構造は公平性を保証し、 /または進行中、他は進まない; 多くのアクセスが読み取り専用の場合にうまく機能するものもあれば、競合が少ない場合にうまく機能するものもあります....そして、この場合は公平性が関係する可能性があります。

テストされていない別の簡単な仮説: InterlockedIncrementMonitor と同じか、それよりも遅いことにもっと驚きました。そのため、キャッシュの一貫性の問題について考えさせられました。結局のところ、Interlocked もターゲット変数でアトミック CAS 操作を使用して実装しているため、書き込み競合がほとんどない場合に最適に機能します。あなたのような書き込みが多いシナリオでは、かなりの量の再試行が必要になります。キャッシュの一貫性を保つために、再試行ごとにコア間バスで大量のトラフィックが生成される可能性があります。Monitor を使用すると、何らかの方法でアクセスをより適切に「シリアル化」し、コア間/プロセス間バスのトラフィックを削減できます。しかし、これはすべて当て推量です:)

于 2013-02-01T08:27:40.557 に答える