Comment std :: lock_guard peut-il être plus rapide que std :: mutex :: lock ()?

9

Je discutais avec un collègue à propos de lock_guard, et il a proposé que lock_guard soit probablement plus lent que mutex :: lock () / mutex :: unlock () en raison du coût d'instanciation et de non-différenciation de la classe lock_guard.

Ensuite, j'ai créé ce test simple et, étonnamment, la version avec lock_guard est presque deux fois plus rapide que la version avec 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;
}

Les résultats sur ma machine:

Take: 41 ms
Take: 22 ms

Quelqu'un peut-il clarifier pourquoi et comment cela peut être?

Eduardo Fernandes
la source
2
et combien de fois avez-vous pris vos mesures?
artm
7
Veuillez poster vos drapeaux de compilateur ... L'analyse comparative dépendra du niveau d'optimisation ...
Macmade
10
Conseil de pro: lorsque vous effectuez des mesures comme celle-ci, permutez la commande pour vous assurer qu'il ne s'agit pas uniquement de données / instructions froides à l'origine du problème: coliru.stacked-crooked.com/a/81f75a1ab52cb1cc
NathanOliver
2
Une autre chose qui est utile lorsque vous effectuez des mesures comme celle-ci: mettez le tout dans une boucle plus grande, de sorte que vous exécutez l'ensemble de mesure entier, disons, 20 fois à chaque exécution. Habituellement, les mesures ultérieures seront celles qui sont réellement significatives, car d'ici là, le cache s'est installé dans le comportement qu'il est susceptible d'avoir à long terme.
Mark Phaedrus
2
Même s'il std::lock_guardétait un peu plus lent, à moins que vous ne puissiez prouver qu'il est important en termes de performances, ce gain de vitesse n'invalidera pas les autres avantages de l'utilisation std::lock_guard(principalement RAII). Si g++quelque chose qui peut jeter ou tout ce qui pourrait se transformer en quelque chose de potentiellement plus compliqué à l'avenir vous presque devez utiliser une sorte d'objet à posséder la serrure.
François Andrieux

Réponses:

6

La version finale produit le même résultat pour les deux versions.

La DEBUGconstruction affiche un temps de ~ 33% plus long pour func2; la différence que je vois dans le démontage qui func2utilise __security_cookieet invoque @_RTC_CheckStackVars@8.

Vous chronométrez DEBUG?

EDIT: De plus, en regardant le RELEASEdémontage, j'ai remarqué que les mutexméthodes étaient enregistrées dans deux registres:

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

et appelé de la même façon des deux func1et func2:

010F1067  call        edi  
....
010F107F  call        ebx  
Vlad Feinstein
la source