Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 非争用线程锁的实际成本_C++_Multithreading_Performance_Thread Safety - Fatal编程技术网

C++ 非争用线程锁的实际成本

C++ 非争用线程锁的实际成本,c++,multithreading,performance,thread-safety,C++,Multithreading,Performance,Thread Safety,首先,在本主题中,我应该强调“完全无争用”线程锁。我很清楚线程进入高度争用的锁、被阻塞和挂起以及不得不使用上下文切换等待轮到它们等等的巨大成本。所以我对争用锁的成本不太感兴趣 “便宜”? 我反复被告知,线程锁在没有竞争的情况下“非常便宜”,但在实际分析中却发现了相反的情况。我想这取决于我们如何定义“真正便宜” 因此,我的要求是提供详细信息,以帮助我理解以更绝对的术语输入和退出非争用线程锁的成本,例如各种类型锁的时钟周期范围(可能有些理论性),如果它与内存访问和缓存有关,等等。我是一个低级的程序员

首先,在本主题中,我应该强调“完全无争用”线程锁。我很清楚线程进入高度争用的锁、被阻塞和挂起以及不得不使用上下文切换等待轮到它们等等的巨大成本。所以我对争用锁的成本不太感兴趣

“便宜”?

我反复被告知,线程锁在没有竞争的情况下“非常便宜”,但在实际分析中却发现了相反的情况。我想这取决于我们如何定义“真正便宜”

因此,我的要求是提供详细信息,以帮助我理解以更绝对的术语输入和退出非争用线程锁的成本,例如各种类型锁的时钟周期范围(可能有些理论性),如果它与内存访问和缓存有关,等等。我是一个低级的程序员,但不完全是在机器/装配级别(尽可能多地提高我在那里的知识)

例如,成本是否与使用通用分配器的堆分配相当?有些人认为便宜,但我认为这是最昂贵的事情可能。这是否可以与分支预测失误相媲美?它是否与内存负载的情况有很大的不同,内存负载可能从缓存线中非常便宜,但对于完全未缓存的DRAM访问来说非常昂贵

在前言中,我想明确一点,我并不是在高瞻远瞩地问这个问题,而是试图对一些我尚未测量过的东西的微观效率着迷。相反,在我花了多年时间在大规模生产代码库中工作之后,我事后才想到这一点,在那里我经常头撞未争用的线程锁,因为实际上,比我预期的要多得多,它是一个主要的热点。因此,我想从更绝对和准确的角度更好地理解性能,特别是帮助我更好地在设计决策方面考虑成本

此外,我对“便宜”的标准可能相当高,因为我通常是在数据结构内部工作的人。例如,许多人似乎认为堆分配相对便宜,而且如果我们将句柄分配给整个数据结构,我会同意。如果我们在一个数据结构中,并且为我们插入到其中的每个元素支付开销,那么它可能会变得非常昂贵。所以我对“贵”和“便宜”的看法可能完全不同

奇怪的代码

我工作过的一个代码库有很长的历史(几十年)。因此,它在很大程度上被设计为只工作单线程,并且有很多实践使得很多基本函数都不是线程安全的(通常甚至不可重入)。一些雄心勃勃的开发人员希望以改进的方式使代码库变得越来越多线程,当然,我们遇到了许多可怕的问题。团队回应:当虫子大量涌入时,将螺纹锁撒在所有地方

我是当时少数几个使用探查器的人之一,经常遇到围绕线程锁的热点,这些线程锁仍然只在完全单线程、无争用的上下文中使用。最初,代码库使用特定于平台的代码,考虑到我主要使用Windows进行开发/测试/评测,锁是Windows API使用的本机关键部分。后来,我们开始使用Qt来减少移植方面的麻烦,关键部分的热点被
QMutex
中的瓶颈所取代。后来,我们开始合并一些Intel的线程构建块,我在
tbb::mutex
中看到了一些热点(虽然没有那么多,但我不确定这是因为我们没有太多地使用它,还是因为它比前两种解决方案更有效:这是一个跨越数百万行代码的庞大代码库)

这是总的部分。我曾经指出一个主要的瓶颈是
QMutex
锁,它完全没有竞争。它只在单线程上下文中使用,锁只是为了线程安全,以防在多线程上下文中使用。所以我的同事像这样“优化”了它(伪代码):

这实际上消除了我们的热点,显著提高了性能,足以让报告速度放缓的用户对结果非常满意!但我想当我看到这一点时,我有点吐在嘴里了。它基于这样一个假设,即这是安全的,因为它是在代码中读取只能从主线程修改的资源

这就是我最开始怀疑无争用线程锁的真正代价的地方,当像上面交换线程ID访问和分支这样奇怪的代码实际上可以消除重大的现实瓶颈时

所以我的最终问题是,一个无争用的线程锁到底有多贵(或者至少比“它很便宜”更贵)


在我看到的案例中,如果我凭直觉(完全意识到这可能是完全错误的),我会说我们正在处理的锁“感觉”就像是在100个周期中的未缓存DRAM访问范围(没有malloc那么贵,但接近那里)。由于人们对硬件/操作系统的细节感兴趣,我通常对广泛的答案感兴趣,因为我们一直在处理多平台项目,但也许我特别感兴趣的是x86/x64、Windows、OSX和Linux。

FWIW:如果一切都以最佳方式实现,那么就可以实现互斥,AFAIR,作为两个原子增量/减量(Windows speak中的联锁*()函数);这些操作依次被转换(在x86上)为带有锁定前缀的asm操作,从而导致总线锁定

反过来,总线锁的实现方式非常不同,在单插槽单核、单插槽多核、带FSB的多插槽以及NUMA/SUMO机器上的行为也可能非常不同。实际上
if (thread_id != main_thread_id)
     mutex.lock();
...
if (thread_id != main_thread_id)
     mutex.unlock();