W jaki sposób std :: lock_guard może być szybszy niż std :: mutex :: lock ()?

9

Kłóciłem się z kolegą na temat lock_guard, a on zaproponował, że lock_guard jest prawdopodobnie wolniejszy niż mutex :: lock () / mutex :: unlock () ze względu na koszt utworzenia i ujednolicenia klasy lock_guard.

Potem stworzyłem ten prosty test i, co zaskakujące, wersja z lock_guard jest prawie dwa razy szybsza niż wersja z mutex :: lock () / mutex :: unlock ()

#include <iostream>
#include <mutex>
#include <chrono>

std::mutex m;
int g = 0;

void func1()
{
    m.lock();
    g++;
    m.unlock();
}

void func2()
{
    std::lock_guard<std::mutex> lock(m);
    g++;
}

int main()
{
    auto t = std::chrono::system_clock::now();
    for (int i = 0; i < 1000000; i++)
    {
        func1();
    }

    std::cout << "Take: " << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - t).count() << " ms" << std::endl;

    t = std::chrono::system_clock::now();
    for (int i = 0; i < 1000000; i++)
    {
        func2();
    }

    std::cout << "Take: " << std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - t).count() << " ms" << std::endl;

    return 0;
}

Wyniki na mojej maszynie:

Take: 41 ms
Take: 22 ms

Czy ktoś może wyjaśnić, dlaczego i jak to może być?

Eduardo Fernandes
źródło
2
i ile razy wykonałeś swoje pomiary?
artm
7
Proszę zamieścić flagi kompilatora ... Benchmarking będzie zależał od poziomu optymalizacji ...
Macmade
10
Pro Wskazówka: Dokonując takich pomiarów, zamień kolejność, aby upewnić się, że przyczyną problemu nie są tylko zimne dane / instrukcje: coliru.stacked-crooked.com/a/81f75a1ab52cb1cc
NathanOliver
2
Kolejna rzecz, która jest pomocna podczas wykonywania takich pomiarów: umieść całość w większej pętli, aby uruchomić cały zestaw pomiarowy, powiedzmy, 20 razy za każdym razem. Zwykle późniejszymi pomiarami będą te, które są rzeczywiście znaczące, ponieważ do tego czasu pamięć podręczna ustaliła się w jakimkolwiek zachowaniu, jakie może mieć w dłuższej perspektywie.
Mark Phaedrus,
2
Nawet jeśli std::lock_guardbył nieco wolniejszy, chyba że możesz udowodnić, że ma on znaczenie pod względem wydajności, to wzrost prędkości nie unieważnia innych korzyści wynikających z używania std::lock_guard(głównie RAII). Jeśli g++jest coś, co może rzucić lub coś, co może zmienić się w coś potencjalnie bardziej skomplikowanego w przyszłości, prawie musisz użyć jakiegoś obiektu do posiadania zamka.
François Andrieux,

Odpowiedzi:

6

Wersja wydania daje taki sam wynik dla obu wersji.

Wersja DEBUGpokazuje ~ 33% dłuższy czas func2; różnica, którą widzę w demontażu, który func2używa __security_cookiei wywołuje @_RTC_CheckStackVars@8.

Czy mierzysz czas DEBUGA?

EDYCJA: Dodatkowo, patrząc na RELEASEdemontaż, zauważyłem, że mutexmetody zostały zapisane w dwóch rejestrach:

010F104E  mov         edi,dword ptr [__imp___Mtx_lock (010F3060h)]  
010F1054  xor         esi,esi  
010F1056  mov         ebx,dword ptr [__imp___Mtx_unlock (010F3054h)]  

i zadzwonił w ten sam sposób z obu func1i func2:

010F1067  call        edi  
....
010F107F  call        ebx  
Vlad Feinstein
źródło