Como o std :: lock_guard pode ser mais rápido que o std :: mutex :: lock ()?

9

Eu estava discutindo com um colega sobre o lock_guard, e ele propôs que o lock_guard é provavelmente mais lento que o mutex :: lock () / mutex :: unlock () devido ao custo de instanciar e desestabilizar a classe lock_guard.

Então criei esse teste simples e, surpreendentemente, a versão com lock_guard é quase duas vezes mais rápida que a versão com 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;
}

Os resultados na minha máquina:

Take: 41 ms
Take: 22 ms

Alguém pode esclarecer por que e como isso pode ser?

Eduardo Fernandes
fonte
2
e quantas vezes você fez suas medições?
Art
7
Por favor envie as suas bandeiras de compilador ... O benchmarking dependerá do nível de otimização ...
Macmade
10
Pro Dica: Ao fazer medições assim, trocar a ordem para se certificar de que não é apenas fria dados / instruções causando o problema: coliru.stacked-crooked.com/a/81f75a1ab52cb1cc
NathanOliver
2
Outra coisa que é útil ao fazer medições como essa: coloque tudo em um loop maior, para que você execute todo o conjunto de medições, digamos, 20 vezes cada execução. Geralmente, as medições posteriores serão realmente significativas, porque, a essa altura, o cache se ajustará ao comportamento que provavelmente terá a longo prazo.
Mark Phaedrus
2
Mesmo que tenha std::lock_guardsido um pouco mais lento, a menos que você possa provar que é importante em termos de desempenho, esse ganho de velocidade não invalidará os outros benefícios do uso std::lock_guard(principalmente RAII). Se g++houver algo que possa ser lançado ou algo que possa se transformar em algo potencialmente mais complicado no futuro, você quase precisará usar algum tipo de objeto para possuir o bloqueio.
François Andrieux

Respostas:

6

A compilação do release produz o mesmo resultado para as duas versões.

A DEBUGcompilação mostra ~ 33% mais tempo para func2; a diferença que vejo na desmontagem que func2usa __security_cookiee invoca @_RTC_CheckStackVars@8.

Você está cronometrando DEBUG?

EDIT: Além disso, ao analisar a RELEASEdesmontagem, notei que os mutexmétodos foram salvos em dois registros:

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

e chamou da mesma maneira de ambos func1e func2:

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