mardi 4 septembre 2018

Another yet std::mutex vs CRITICAL_SECTION comparison

I read many articles regarding the std::mutex and CRITICAL_SECTION comparison, and after all I decided to implement and run my own tests to compare std::mutex vs CRITICAL_SECTION vs SRWLock.

You can find the C++ program at the bottom of the question. The project was built as Windows Console Application on Microsoft Visual Studio 17 (15.8.2) with the folowing settings:

  • Use of MFC: Use MFC in a Static Library
  • Windows SDK Version: 10.0.17134.0
  • Platform Toolset: Visual Studio 2017 (v141)
  • Optimization: O2, Oi, Oy-, GL

Then I copied the executable file to several platforms with Intel Core i7-2600 3.40GHz (VMWare) and run tests:

  • Windows Vista x64
  • Windows 7 x64
  • Windows 8 x64
  • Windows 10 x64

The results are on the image below. The X-axis is the number of concurrent threads. The Y-axis is the elapsed time in seconds (lower is better).

Test results

What we can see:

  • SRWLock performance is approximately the same for all platforms
  • CriticalSection is much better than std::mutex on Win Vista
  • CriticalSection is a little bit better than std::mutex (or the same) on Windows 7
  • CriticalSection is much worse than std::mutex on Windows 8 and higher

Questions:

  1. As far as I know std::mutex is implemented via SRWLock on Win10, but std::mutex is a twice worse than SRWLock on Win10. Why does it happen?
  2. Why does std::mutex so bad on Win Vista?
  3. I can explain (maybe) the fact that std::mutex is becoming better with every new Windows version, e.g. implementation improvemets or whatever. But I don't understand why does CS performance became worse. Why does it happen?

#include <chrono>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
#include <vector>

#include <Windows.h>

const size_t T = 10;
const size_t N = 3000000;
volatile uint64_t var = 0;

const std::string sep = ";";

namespace WinApi
{
    class CriticalSection
    {
        CRITICAL_SECTION cs;
    public:
        CriticalSection() { InitializeCriticalSection(&cs); }
        ~CriticalSection() { DeleteCriticalSection(&cs); }
        void lock() { EnterCriticalSection(&cs); }
        void unlock() { LeaveCriticalSection(&cs); }
    };

    class SRWLock
    {
        SRWLOCK srw;
    public:
        SRWLock() { InitializeSRWLock(&srw); }
        void lock() { AcquireSRWLockExclusive(&srw); }
        void unlock() { ReleaseSRWLockExclusive(&srw); }
    };
}

template <class M>
void doLock(void *param)
{
    M &m = *static_cast<M*>(param);
    for (size_t n = 0; n < N; ++n)
    {
        m.lock();
        var += std::rand();
        m.unlock();
    }
}

template <class M>
void runTest(size_t threadCount)
{
    M m;
    std::vector<std::thread> thrs(threadCount);

    const auto start = std::chrono::system_clock::now();

    for (auto &t : thrs) t = std::thread(doLock<M>, &m);
    for (auto &t : thrs) t.join();

    const auto end = std::chrono::system_clock::now();

    const std::chrono::duration<double> diff = end - start;
    std::cout << diff.count() << sep;
}

template <class ...Args>
void runTests(size_t threadMax)
{
    {
        int dummy[] = { (std::cout << typeid(Args).name() << sep, 0)... };
        (void)dummy;
    }
    std::cout << std::endl;

    for (size_t n = 1; n <= threadMax; ++n)
    {
        {
            int dummy[] = { (runTest<Args>(n), 0)... };
            (void)dummy;
        }
        std::cout << std::endl;
    }
}

int main()
{
    std::srand(time(NULL));
    runTests<std::mutex, WinApi::CriticalSection, WinApi::SRWLock>(T);
    return 0;
}

Aucun commentaire:

Enregistrer un commentaire