我一直在测试Windows SRW锁定性能并发现了一个奇怪的问题。我有以下测试循环:
for (int i = 0; i < 100000000; ++i)
{
AcquireSRWLockShared (&g_srwLock);
ReleaseSRWLockShared (&g_srwLock);
}
当我通过一个线程运行它需要大约1.5秒,当我同时运行两个线程时,大约需要2.9s(每个线程)。好的,那么我有以下循环:
for (int i = 0; i < 100000000; ++i)
{
_InterlockedIncrement (&g_state);
_InterlockedDecrement (&g_state);
}
当我用一个线程运行它时大约需要1.1s,当我用两个线程运行它时大约需要5.6s(!!!,每个线程)。我做错了什么?
我挖掘了AcquireSRWLockShared代码并发现它使用了锁cmpxchg,所以我尝试了它的循环:
for (int i = 0; i < 100000000; ++i)
{
_InterlockedCompareExchange (&g_state, 0, 0);
_InterlockedCompareExchange (&g_state, 0, 0);
}
并得到完全相同的结果 - 两个线程约为5.6秒。好的,然后我复制了AcquireSRWLockShared的确切代码:
__declspec (naked) void __stdcall TestLock (volatile long *address)
{
__asm
{
mov edi,edi
push ebp
mov ebp,esp
push esi
mov esi,dword ptr [ebp+8]
push 11h
xor ecx,ecx
mov edx,esi
pop eax
lock cmpxchg dword ptr [edx],ecx
mov ecx,eax
cmp ecx,11h
//jne 77685820
pop esi
pop ebp
ret 4
}
}
(我必须评论跳转,因为它转到其他一些代码),并且两个线程再次获得5.6s。那么,有什么不对?为什么从库中运行时相同的代码需要2.9s,从我的函数运行时需要5.6s?
我的电脑是i5-3570K @ 4.4GHz,16Gb DDR3 RAM @ 1600MHz。
答案 0 :(得分:0)
考虑锁定增量在微代码级别的工作原理:获取包含变量的内存缓存行的锁定,将值拉入寄存器,增加值,将其重新放回内存。这样做的副作用是,如果在另一个核心上运行的另一个线程在其本地缓存中具有相同的内存缓存行,则该缓存行必须驱逐(即丢弃)然后从内存重新加载(或者至少从较高的缓存级别开始。)
比较锁定交换大致相同,只是修改是有条件的:它仍然需要抓住缓存行上的锁并将其拉入内存,如果比较成功,那么它最终几乎与锁定增量。但是如果比较失败,则不会修改高速缓存行,因此它不从其他核心的高速缓存中逐出。
在第一个示例(AcquireSRWLockShared后跟ReleaseSRWLockShared)中,锁定变量在每次迭代中被修改两次,但是由单线程(单核)修改。这是因为虽然另一个线程在其保持时尝试获取锁定时将采用总线锁定,但比较失败,因此不进行修改,因此不会从线程中删除高速缓存行/拿着锁的核心。最终当该线程释放锁时,缓存行被另一个核心驱逐并重新加载,但每个获取/释放对只发生一次。
在其他示例中,您有两个线程都尝试(并且可能大部分成功)锁定高速缓存行并对其进行修改,从而导致其他核心的高速缓存行被驱逐。并且你在每个循环中都做了两次,因为递增/递减总是成功(与[0,0]的比较交换一样),所以你经常将闪存行驱逐两次。 / p>