我正在编写C ++代码,并在我的代码中使用了很多内存屏障/围栏。我知道,MB告诉编译器和硬件不要重新排序它周围的写/读。但我不知道这个操作在运行时对于处理器有多复杂。
我的问题是:这种障碍的运行时开销是多少?我没有找到任何与谷歌有用的答案... 开销可以忽略不计吗?或者导致MB的大量使用导致严重的性能问题?
最好的问候。
答案 0 :(得分:2)
与算术和“正常”指令相比,我理解这些指令非常昂贵,但没有数字来备份该声明。我喜欢jalf的回答,描述了指令的效果,并想补充一点。
通常存在一些不同类型的障碍,因此理解差异可能会有所帮助。例如在清除锁定字(例如ppc上的lwsync或ia64上的st4.rel)之前的互斥实现中需要像jalf所提到的那样的屏障。所有读取和写入都必须完成,并且只能执行管道中稍后没有内存访问且不依赖于正在进行的内存操作的指令。
另一种类型的障碍是您在获取锁时在互斥实现中使用的类别(示例,ppc上的isync或ia64上的instr.acq)。这会对将来的指令产生影响,因此如果预取了非依赖性负载,则必须将其丢弃。例如:
if ( pSharedMem->atomic.bit_is_set() ) // use a bit to flag that somethingElse is "ready" { foo( pSharedMem->somethingElse ) ; }
如果没有获取障碍(借用ia64术语),如果在检查标记位检查完成之前,如果有东西将其放入寄存器,则程序可能会出现意外结果。
存在第三种类型的屏障,通常较少使用,并且需要强制执行商店负载排序。这种排序强制执行指令的指令示例是,ppc(重量级同步)上的同步,ia64上的MF,sparc上的membar #storeload(即使是TSO也需要)。
使用类似伪代码的ia64来说明,假设有一个
st4.rel ld4.acq
在没有mf之间没有保证负载跟随商店。你知道st4rel之前的加载和存储是在那个商店或“后续”加载之前完成的,但是那个加载或其他未来的加载(也许是非依赖的存储?)可以潜入,之前完成,因为什么都没有阻止否则。
因为互斥实现很可能只在其实现中使用获取和释放障碍,所以我希望这可观察到的效果是锁定释放后的内存访问实际上有时会发生在“仍在关键部分”中。
答案 1 :(得分:1)
尝试思考指令的作用。它不会使CPU在逻辑方面做任何复杂的事情,但它迫使它等到所有读写都被提交到主存储器。因此,成本实际上取决于访问主存储器的成本(以及未完成的读/写次数)。
访问主内存通常非常昂贵(10-200个时钟周期),但从某种意义上说,这项工作必须在没有屏障的情况下完成,它可以通过同时执行其他指令来隐藏,所以你没有不太感到费用。
它还限制了CPU(和编译器)重新安排指令的能力,因此可能存在间接成本,因为附近的指令不能交错,否则可能会产生更高效的执行计划。