Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why everyone states that SpinLock is faster? [closed]

I have read a lot of docs and articles and posts all over the internet. Almost everyone and everywhere commits that SpinLock is faster for a short running pieces of code, but I made a test, and it appears to me that simple Monitor.Enter works faster than SpinLock.Enter (Test is compiled against .NET 4.5)

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;

        Console.WriteLine("Cores/processors count: {0}", Environment.ProcessorCount);

        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;
    }
}

On a server with 24 cores of 2.5 GHz this application compiled with x64 produced the following results:

Cores/processors count: 24
Test with interlocked: 1373.0829 ms
Test with SpinLock: 10894.6283 ms
Test with Monitor: 1171.1591 ms
like image 627
Rauf Avatar asked Jan 30 '13 18:01

Rauf


People also ask

Is SpinLock faster than mutex?

Mutexes Are Faster Than Spinlocks.

Why are spin locks not fair?

Typically, the performance of fair spinlocks drops extremely when the number of threads competing for the same lock is greater than the number of CPU cores in the system. The reason for this is that the thread which is next in the sequence to acquire the lock might be sleeping.

What is the advantage of ticket locks over simple spin locks?

One advantage of a ticket lock over other spinlock algorithms is that it is fair. The waiting threads are processed in a first-in first-out basis as the dequeue ticket integer increases, thus preventing starvation.

What is the difference between mutex and SpinLock?

Spinlock is a type of lock that causes a thread attempting to obtain it to check for its availability while waiting in a loop continuously. On the other hand, a mutex is a program object designed to allow different processes may take turns sharing the same resource.


1 Answers

You are just not testing a scenario where SpinLock can improve the threading. The core idea behind a spin-lock is that a thread-context switch is very expensive operation, costing between 2000 and 10,000 cpu cycles. And that if it is likely that a thread can acquire a lock by waiting for a bit (spinning) then the extra cycles burned waiting can pay off by avoiding the thread context switch.

So basic requirements is that the lock is held for a very short time, which is true in your case. And that there are reasonable odds that the lock can be acquired. Which is not true in your case, the lock is heavily contested by no less than 24 threads. All spinning and burning core without having a chance to acquire the lock.

In this test Monitor will work best since it queues threads waiting to acquire the lock. They are suspended until one of them has a chance to acquire the lock, released from the wait queue when the lock is released. Giving them all a fair chance to take a turn, thus maximizing the odds that they'll all finish at the same time. Interlocked.Increment is not bad either but can't provide a fairness guarantee.

It can be pretty hard to judge whether Spinlock is the right approach up front, you have to measure. A concurrency analyzer is the right kind of tool.

like image 189
Hans Passant Avatar answered Sep 21 '22 15:09

Hans Passant