Wie kann std :: lock_guard schneller sein als std :: mutex :: lock ()?

9

Ich habe mit einem Kollegen über lock_guard gestritten, und er schlug vor, dass lock_guard aufgrund der Kosten für die Instanziierung und Unistantiierung der Klasse lock_guard wahrscheinlich langsamer als mutex :: lock () / mutex :: refresh () ist.

Dann habe ich diesen einfachen Test erstellt und überraschenderweise ist die Version mit lock_guard fast zweimal schneller als die Version mit mutex :: lock () / mutex :: entsperren ()

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

Die Ergebnisse auf meiner Maschine:

Take: 41 ms
Take: 22 ms

Kann jemand klären, warum und wie das sein kann?

Eduardo Fernandes
quelle
2
und wie oft haben Sie Ihre Messungen vorgenommen?
Artm
7
Bitte posten Sie Ihre Compiler-Flags ... Das Benchmarking hängt von der Optimierungsstufe ab ...
Macmade
10
Pro-Tipp: Wenn Sie Messungen wie diese durchführen, tauschen Sie die Reihenfolge aus, um sicherzustellen, dass nicht nur kalte Daten / Anweisungen das Problem verursachen: coliru.stacked-crooked.com/a/81f75a1ab52cb1cc
NathanOliver
2
Eine andere Sache, die bei solchen Messungen hilfreich ist: Legen Sie das Ganze in eine größere Schleife, sodass Sie den gesamten Messsatz beispielsweise 20 Mal pro Lauf ausführen. Normalerweise sind die späteren Messungen diejenigen, die tatsächlich von Bedeutung sind, da sich der Cache bis dahin auf das Verhalten eingestellt hat, das auf lange Sicht wahrscheinlich ist.
Mark Phaedrus
2
Selbst wenn std::lock_guardes etwas langsamer war, wird dieser Geschwindigkeitsgewinn die anderen Vorteile der Verwendung std::lock_guard(hauptsächlich RAII) nicht ungültig machen, es sei denn, Sie können nachweisen, dass es auf die Leistung ankommt . Wenn g++ist alles , was werfen oder etwas kann , die in etwas ändern könnten möglicherweise komplizierte mehr in der Zukunft , die Sie fast haben , um irgendeine Art von Objekt zu verwenden , um das Schloss zu besitzen.
François Andrieux

Antworten:

6

Der Release-Build führt für beide Versionen zum gleichen Ergebnis.

Der DEBUGBuild zeigt ~ 33% längere Zeit für func2; Der Unterschied, den ich in der Demontage sehe, die func2verwendet __security_cookieund aufruft @_RTC_CheckStackVars@8.

Planen Sie DEBUG?

BEARBEITEN: Außerdem habe RELEASEich bei der Demontage festgestellt, dass mutexMethoden in zwei Registern gespeichert wurden:

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

und nannte den gleichen Weg von beiden func1und func2:

010F1067  call        edi  
....
010F107F  call        ebx  
Vlad Feinstein
quelle