std::lock_guard如何比std::mutex::lock()更快?

10

我和一位同事就lock_guard进行了争论,他认为由于实例化和销毁lock_guard类的成本,lock_guard可能比mutex::lock() / mutex::unlock()慢。

然后我创建了这个简单的测试,令人惊讶的是,使用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
请发布您的编译器标志...基准测试将取决于优化级别... - Macmade
11
专业提示:在进行类似测量时,交换顺序以确保问题不仅是由于冷数据/指令所导致的:http://coliru.stacked-crooked.com/a/81f75a1ab52cb1cc - NathanOliver
2
在进行此类测量时,还有一个有用的方法:将整个过程放入一个更大的循环中,以便每次运行对整个测量集运行20次。通常,后面的测量结果才是真正有意义的,因为此时缓存已经稳定下来,具有长期可能性的任何行为都会呈现出来。 - Mark Phaedrus
2
即使 std::lock_guard 稍微慢一些,除非你能证明在性能方面它很重要,否则这种速度提升不会使使用 std::lock_guard 的其他好处失效(主要是 RAII)。如果 g++ 可以抛出任何异常或将来可能变得更加复杂,你几乎必须使用某种对象来拥有锁。 - François Andrieux
显示剩余2条评论
1个回答

6
发布版本和调试版本的结果相同。 DEBUG 版本的 func2 方法的时间比较发布版本长约33%;我在反汇编中看到,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)]  

这个函数从func1func2中都可以调用,并且使用相同的方法名:

010F1067  call        edi  
....
010F107F  call        ebx  

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接