【发布时间】:2014-06-08 03:49:52
【问题描述】:
设置:Intel Ivy Bridge Core i7,在 64 位模式下编译,MSVC(2012) 和 Win 7 64 位。
我试图了解原子增量是否会导致缓存未命中。
我设置了一个测试,其中一个原子变量和另一个变量在同一个缓存行中,而不是在同一个缓存行中,然后比较缓存未命中。代码和结果如下。
结果
不同的缓存行:
- 原子增量没有 L1 缓存未命中
-
d.a的两个增量都有 40-50% 的 L1 缓存未命中。
相同的缓存行
- 递增
d.a没有缓存未命中 - 递增原子遇到100% L1 缓存未命中。
谁能解释一下?!我期待当原子与 d.a 位于同一缓存行时,d.a 将遭受 100% 的缓存未命中,而当它们位于不同的缓存行时 d.a 不会受到影响。
#include <atomic>
#include <iostream>
#include <iomanip>
#include <vector>
//Structure to ensure not in same cache line
__declspec(align(64)) struct S{
volatile double a,b,d,c,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t;
volatile std::atomic<short> atom;
};
//Structure to ensure same cache line
/*__declspec(align(64)) struct S{
volatile std::atomic<short> atom;
volatile short a;
};*/
int main(){
volatile S d;
for(long long i=0; i<1000000000; i++){
d.a++;
d.atom++;
d.a++;
}
}
这里更新了一些asm:
/* _Atomic_fetch_add_2, _Atomic_fetch_sub_2 */
inline _Uint2_t _Fetch_add_seq_cst_2(volatile _Uint2_t *_Tgt, _Uint2_t _Value)
{ /* add _Value to *_Tgt atomically with
mov word ptr [_Tgt],dx
mov qword ptr [rsp+8],rcx
push rdi
sequentially consistent memory order */
return (_INTRIN_SEQ_CST(_InterlockedExchangeAdd16)((volatile short *)_Tgt, _Value));
movzx eax,word ptr [_Value]
mov rcx,qword ptr [_Tgt]
lock xadd word ptr [rcx],ax
}
pop rdi
【问题讨论】:
-
如果您比较原子/互锁 API 及其在 Linux、FreeBSD 和 Windows 以及可能在其他操作系统上的行为,您会发现您提供的代码没有提及您运行代码的位置可能会成功不可能给你的问题一个好的答案。另外,我想知道您如何测量缓存未命中。也许,这将有助于使用低级操作系统特定 API 重现代码。可以选择如何使用原子增量。例如,在 Windows 上,存在 NUMA 特定的变体。主要是 InterlockedIncrement/Acquire/Release 等。
#include <atomic>并没有显示所有这些。 -
已添加更多详细信息 - 使用 Win 7 和 MSVC 11 (2012)。使用英特尔 VTune 测量缓存未命中。
-
深入内部直到找到“牛肉”,产生原子运算符 ++ 使用 _InterlockedExchangeAdd16 。阅读MSDN: InterlockedExchangeAdd() 并希望 16 位版本能够相应地工作,您选择了 Windows 互锁 API 的“完全内存屏障”版本。此评论旨在为其他人节省一些时间来了解他们实际在看什么。
-
为了排除您对 L1 缓存行大小的假设确实与您的结构内容相匹配的可能性,您可以使用 How to find out cache line size 在代码中添加一些运行时检查。跨度>
标签: c++ multithreading performance caching atomic