- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
首先,我应该重点介绍该主题中的_completely unconended_线程锁。我很清楚线程进入高竞争性锁,被阻塞和挂起以及不得不等待上下文切换等等的巨大代价。因此,我对争用锁的成本并不十分感兴趣。
“便宜”吗?
我一再被告知,线程锁在无竞争的情况下“确实很便宜”,但实际上在进行概要分析时却发现相反的情况。我想这取决于我们如何定义“真正便宜”。
因此,我的要求是提供详细信息,这些信息可以帮助我理解更绝对的术语,即进入和退出无竞争线程锁的成本,例如各种类型的锁的时钟周期范围(可能有些理论上)(如果它与内存访问和存储相关)。缓存等。我有点低级的编码器,但不是完全在机器/组装级别(试图在这里尽可能多地提高我的知识)。
例如,成本与使用通用分配器进行堆分配是否可比?有人认为这很便宜,但我认为它可能是最昂贵的东西之一。它可与分支错误预测相提并论吗?它是否像在高速缓存行中可能非常便宜但对于完全未缓存的DRAM访问而言非常昂贵的内存负载情况发生巨大变化?
作为序言,我想明确地说,我并不是出于有远见的要求,而是试图对我尚未衡量的事物的微观效率进行痴迷。相反,在大型生产代码库中工作了很多年后,我经常回想起这个问题,在该代码库中,我经常碰到无人争先的线程锁,实际上这比我预期的要频繁得多,这是一个主要的热点。因此,我想从绝对和准确的角度更好地理解性能,尤其是要帮助我更好地考虑设计决策方面的成本。
同样,我对构成“廉价”商品的标准可能很高,因为我通常是在数据结构内部工作的人。例如,许多人似乎认为堆分配相对便宜,并且如果我们将句柄分配给整个数据结构,我会同意的。如果我们在数据结构内部,并为插入其中的每个元素支付开销,那么它可能会变得非常昂贵。因此,我对“昂贵”和“便宜”的想法可能完全不同。
奇怪的代码
我工作过的其中一个代码库有很长的遗产(数十年)。因此,它在很大程度上被设计为只能通过许多实践使单线程工作,这甚至使许多基本功能都不是线程安全的(甚至甚至不是可重入的)。那里一些雄心勃勃的开发人员希望以一种改进的方式使该代码库越来越多线程化,当然,我们遇到了许多可怕的问题。团队响应:随着漏洞泛滥,在各处散布线程锁。
我当时是使用概要分析器的少数人之一,并且经常遇到围绕线程锁旋转的热点,而这些线程锁仍仅在完全单线程,无竞争的上下文中使用。最初,代码库使用的是平台特定的代码,并且鉴于我主要使用Windows进行开发/测试/分析,所以锁是Windows API使用的 native 关键部分。后来,我们开始使用Qt来减轻可移植性的麻烦,并且QMutex
中的瓶颈取代了关键部分的热点。然后,后来我们开始合并一些英特尔的线程构建模块,我在tbb::mutex
中看到了一些热点(尽管不是很多,但是我不确定这是否是因为我们没有使用太多或效率更高)比前两种解决方案要好:这是一个跨越数百万行代码的庞大代码库)。
这是最重要的部分。我曾经指出一个主要瓶颈在QMutex
锁中,这是完全没有争议的。它仅在单线程上下文中使用,并且该锁仅用于线程安全,以防曾经在多线程上下文中使用过。因此,我的同事就这样“优化”了它(伪代码):
if (thread_id != main_thread_id)
mutex.lock();
...
if (thread_id != main_thread_id)
mutex.unlock();
最佳答案
FWIW:如果一切都以最佳方式实现,则互斥体可以AFAIR的形式实现为两个原子增量/减量(在Windows操作系统中为Interlocked *()函数);它们依次(在x86上)转换为带有LOCK前缀的asm操作,从而导致总线锁定。
反过来,在单插槽单核,单插槽多核,带FSB的多插槽和NUMA/SUMO机器上,总线锁定的实现方式也大不相同,而MIGHT的行为也有很大不同。但是实际上,对于多路 socket ,我已经看到了大约100个时钟,对于单路 socket ,我看到了几十个时钟。注意:这些是极其粗略的数字,在您使用RDTSC之类的工具(完全在目标硬件上)进行自己的测量之前,请不要认为它们是理所当然的。
P.S.即使仅只能在主线程中修改数据,您提供的代码段(带有if(thread_id!= main_thread_id))也可能是不安全的。
关于c++ - 无竞争线程锁的实际成本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30528493/
我是一名优秀的程序员,十分优秀!