0

可能重复:
为什么每个人都说 SpinLock 更快?

这个问题是关于 SpinLock、Monitor & Interlocked。

我做了 2 个测试来测试 的性能,Monitor这些测试让我感到困惑。SpinLockInterlocked

我的困惑特别是关于SpinLock真正的速度有多快。根据我的测试SpinLock,它比Monitor. 但是基于一些文档和文章SpinLock应该提供性能增益。

现在我想知道在哪些情况下会SpinLock提高性能?

您可以在下面找到有关我执行的测试的一些详细信息:

在第一次测试中,我创建了几个线程(就像我拥有的​​许多硬件线程一样)访问同一个共享锁对象,执行非常短的操作(或根本没有操作:这只是一个测试)。

在第二个测试中,我创建了一个元素数组,并且很少有线程随机访问该数组中的元素。每个元素都包含自己的锁定对象:System.Objectfor Monitortest,SpinLockobject for SpinLocktest,对于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有用的代码(设备驱动程序开发除外)?

4

1 回答 1

2

如果对资源的争用较少(即几乎总是成功地获得对资源的锁定),SpinLock 会非常快。参考:Joe Duffy 书籍和博客http://www.bluebytesoftware.com/blog/

在每个测试中,对共享区域的访问都是循环执行的

_可能_意味着争用很高;(顺便说一句,你能发布一个完整的代码示例吗?这将有助于减少所需的“猜测”)。因此,SpinLock 很可能会旋转,然后等待 - 使其比直接等待的 Monitor 更糟糕。

编辑:在阅读了您已关闭的相关问题的详细信息后:我完全同意Hans Passant 的回答

所以基本要求是锁的持有时间很短,这在你的情况下是正确的。并且有合理的几率可以获得锁。在您的情况下不是这样,锁受到不少于 24 个线程的激烈竞争。

盲目地使用 SpinLock,没有测量和/或至少不理解其设计背后的原理,是一种过早优化的情况,它可以快速运行到实际上更慢甚至不正确的代码:记住,一些同步结构保证公平和/ 或进度,其他没有;当大量访问是只读的时,有些工作得更好,有些在争用较低时工作得更好,......在这种情况下,公平可能是相关的。

只是另一个快速的、未经检验的假设:我更惊讶的InterlockedIncrement是它比 Monitor 更慢或相等。这让我想到了缓存一致性问题;毕竟,当写入争用很少时,Interlocked 也最有效,因为它是在目标变量上使用原子 CAS 操作来实现的。在像您这样的写入繁重的场景下,它将需要大量的重试,每次重试结束可能会在内核间总线上产生大量流量以保持缓存的一致性。使用 Monitor 可以以某种方式更好地“序列化”访问,从而减少内核间/进程间总线上的流量。但这一切都只是猜测:)

于 2013-02-01T08:27:40.557 回答