Как std :: lock_guard может быть быстрее, чем std :: mutex :: lock ()?

9

Я спорил с коллегой о lock_guard, и он предположил, что lock_guard, вероятно, медленнее, чем mutex :: lock () / mutex :: unlock () из-за стоимости создания и удаления класса lock_guard.

Затем я создал этот простой тест, и, что удивительно, версия с lock_guard почти в два раза быстрее, чем версия с 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;
}

Результаты на моей машине:

Take: 41 ms
Take: 22 ms

Может кто-нибудь уточнить, почему и как это может быть?

Эдуардо Фернандес
источник
2
и сколько раз вы делали измерения?
artm
7
Пожалуйста,
оставьте
10
Совет для профессионалов: при выполнении таких измерений поменяйте порядок, чтобы убедиться, что это не просто холодные данные / инструкции, вызывающие проблему: coliru.stacked-crooked.com/a/81f75a1ab52cb1cc
NathanOliver
2
Еще одна вещь, которая полезна при выполнении таких измерений: поместите все это в большую петлю, чтобы вы выполняли весь набор измерений, скажем, 20 раз за каждый цикл. Обычно последующие измерения будут действительно значимыми, потому что к тому времени кэш-память установит поведение, которое, вероятно, будет иметь место в долгосрочной перспективе.
Марк
2
Даже если бы он std::lock_guardбыл немного медленнее, если только вы не докажете, что это имеет значение с точки зрения производительности, это увеличение скорости не аннулирует другие преимущества использования std::lock_guard(в основном RAII). Если g++есть что-то, что может бросить или что-то, что может превратиться в нечто потенциально более сложное в будущем, вы почти должны использовать какой-то объект для владения блокировкой.
Франсуа Андрие

Ответы:

6

Сборка релиза дает одинаковый результат для обеих версий.

В DEBUGсборки показывает ~ 33% больше времени для func2; Разница я вижу в разборке, которая func2использует __security_cookieи вызывает @_RTC_CheckStackVars@8.

Вы рассчитываете DEBUG?

РЕДАКТИРОВАТЬ: Кроме того, глядя на RELEASEразборки, я заметил, что mutexметоды были сохранены в двух реестрах:

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

и называется одинаково от обоих func1и func2:

010F1067  call        edi  
....
010F107F  call        ebx  
Влад Файнштейн
источник