¿Cómo puede std :: lock_guard ser más rápido que std :: mutex :: lock ()?

9

Estaba discutiendo con un colega, sobre lock_guard, y él propuso que lock_guard es probablemente más lento que mutex :: lock () / mutex :: unlock () debido al costo de instanciar y unificar la clase lock_guard.

Luego creé esta prueba simple y, sorprendentemente, la versión con lock_guard es casi dos veces más rápida que la versión con 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;
}

Los resultados en mi máquina:

Take: 41 ms
Take: 22 ms

¿Alguien puede aclarar por qué y cómo puede ser esto?

Eduardo Fernandes
fuente
2
y cuantas veces tomaste tus medidas?
artm
77
Publique las
10
Consejo profesional: cuando realice mediciones como esta, cambie el orden para asegurarse de que no solo los datos / instrucciones fríos causen el problema: coliru.stacked-crooked.com/a/81f75a1ab52cb1cc
NathanOliver
2
Otra cosa que es útil cuando se realizan mediciones como esta: coloque todo en un bucle más grande, para que ejecute todo el conjunto de mediciones, por ejemplo, 20 veces en cada ejecución. Por lo general, las mediciones posteriores serán las que realmente sean significativas, porque para entonces el caché se ha adaptado a cualquier comportamiento que pueda tener a largo plazo.
Mark Phaedrus el
2
Incluso si std::lock_guardfue un poco más lento, a menos que pueda probar que es importante en términos de rendimiento, esa ganancia de velocidad no invalidará los otros beneficios del uso std::lock_guard(principalmente RAII). Si g++hay algo que puede arrojar o algo que podría cambiar en algo potencialmente más complicado en el futuro, casi tiene que usar algún tipo de objeto para poseer la cerradura.
François Andrieux

Respuestas:

6

La versión de lanzamiento produce el mismo resultado para ambas versiones.

La DEBUGconstrucción muestra ~ 33% más tiempo para func2; La diferencia que veo en el desmontaje que func2utiliza __security_cookiee invoca @_RTC_CheckStackVars@8.

¿Estás cronometrando DEBUG?

EDITAR: Además, al mirar el RELEASEdesmontaje, noté que los mutexmétodos se guardaron en dos registros:

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

y llamado de la misma manera desde ambos func1y func2:

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