7

昨天我发布了这个关于如何编写快速自旋锁的问题。感谢 Cory Nelson,我似乎找到了一种优于我的问题中讨论的其他方法的方法。我使用该CMPXCHG指令来检查锁是否为 0,因此是空闲的。CMPXCHG在“字节”上运行,WORD并且DWORD。我会假设该指令在BYTE. 但是我写了一个锁来实现每种数据类型:

inline void spin_lock_8(char* lck)
{
    __asm
    {
        mov ebx, lck                        ;move lck pointer into ebx
        xor cl, cl                          ;set CL to 0
        inc cl                              ;increment CL to 1
        pause                               ;
        spin_loop:
        xor al, al                          ;set AL to 0
        lock cmpxchg byte ptr [ebx], cl     ;compare AL to CL. If equal ZF is set and CL is loaded into address pointed to by ebx
        jnz spin_loop                       ;jump to spin_loop if ZF
    }
}
inline void spin_lock_16(short* lck)
{
    __asm
    {
        mov ebx, lck
        xor cx, cx
        inc cx
        pause
        spin_loop:
        xor ax, ax
        lock cmpxchg word ptr [ebx], cx
        jnz spin_loop
    }
}
inline void spin_lock_32(int* lck)
{
    __asm
    {
        mov ebx, lck
        xor ecx, ecx
        inc ecx
        pause
        spin_loop:
        xor eax, eax
        lock cmpxchg dword ptr [ebx], ecx
        jnz spin_loop
    }
}
inline spin_unlock(<anyType>* lck)
{
    __asm
    {
        mov ebx, lck
        mov <byte/word/dword> ptr [ebx], 0
    }
}

然后使用以下伪代码测试锁(请注意,lcm 指针始终指向可被 4 整除的地址):

<int/short/char>* lck;
threadFunc()
{
    loop 10,000,000 times
    {
        spin_lock_8/16/32 (lck);
        spin_unlock(lck);
    }
}
main()
{
    lck = (char/short/int*)_aligned_malloc(4, 4);//Ensures memory alignment
    start 1 thread running threadFunc and measure time;
    start 2 threads running threadFunc and measure time;
    start 4 threads running threadFunc and measure time;
    _aligned_free(lck);
}

我在具有 2 个能够运行 4 个线程的物理内核(Ivy Bridge)的处理器上以毫秒为单位测量了以下结果。

           1 thread    2 threads     4 threads
8-bit      200         700           3200
16-bit     200         500           1400
32-bit     200         900           3400

数据表明,所有功能都需要相同的时间来执行。但是当多个线程必须检查时,如果lck == 0使用 16 位可以明显更快。这是为什么?我不认为这与lck?

提前致谢。

4

2 回答 2

2

想象一下有 1234 个线程和 16 个 CPU。一个线程获取自旋锁,然后操作系统进行任务切换。现在您有 16 个 CPU,每个 CPU 都运行剩余的 1233 个线程中的一个,所有这些都以一种非常无意义的方式旋转,无论操作系统需要多长时间才能将 CPU 时间还给唯一可以释放自旋锁的线程。这意味着整个操作系统基本上可以锁定几秒钟(所有 CPU 都完全耗尽)。这是严重的迟缓;那你怎么修呢?

您可以通过不在用户空间中使用自旋锁来修复它。只有当/当可以禁用任务切换时,才应该使用自旋锁;并且只有内核应该能够禁用任务切换。

更具体地说,您需要使用互斥锁。现在互斥锁可能会在放弃并使线程等待锁之前开始旋转,并且(对于典型/低争用情况)这确实有帮助,但它仍然是互斥锁而不是自旋锁。

下一个; 对于健全的软件,重要的(对于性能)是避免锁争用,然后确保无争用情况很快(如果没有争用,一个好的互斥锁不会导致任务切换)。您正在衡量有争议的/不相关的案例。

最后; 你的锁坏了。为避免过度使用lock前缀,您应该测试是否可以在没有任何lock前缀的情况下获取,并且只有在您可能能够获取时才使用lock前缀。英特尔(可能还有很多其他人)将此策略称为“测试;然后(测试和设置)”。此外,您未能理解pause(或“rep nop”对于那些糟糕到不支持 10 年旧指令的汇编程序)的目的。

一个像样的自旋锁可能看起来像:

acquire:
    lock bts dword [myLock],0   ;Optimistically attempt to acquire
    jnc .acquired               ;It was acquired!
.retry:
    pause
    cmp dword [myLock],0        ;Should we attempt to acquire again?
    jne .retry                  ; no, don't use `lock`
    lock bts dword [myLock],0   ;Attempt to acquire
    jc .retry                   ;It wasn't acquired, so go back to waiting
.acquired:
    ret

release:
    mov dword [myLock],0        ;No lock prefix needed here as "myLock" is aligned
    ret

另请注意,如果您未能充分减少锁争用的机会,那么您确实需要关心“公平”并且不应该使用自旋锁。“不公平”自旋锁的问题在于,有些任务可能很幸运,总是得到锁,而有些任务可能很不幸,永远不会得到锁,因为幸运的任务总是得到它。对于竞争激烈的锁来说,这一直是一个问题,但对于现代 NUMA 系统来说,它更可能成为一个问题。在这种情况下,您至少应该使用票证锁。

票证锁的基本思想是确保任务按照它们到达的顺序获得锁(而不是一些“可能非常糟糕”的随机顺序)。为了完整起见,票证锁可能如下所示:

acquire:
    mov eax,1
    lock xadd [myLock],eax           ;myTicket = currentTicket, currentTicket++

    cmp [myLock+4],eax               ;Is it my turn?
    je .acquired                     ; yes
.retry:
    pause
    cmp [myLock+4],eax               ;Is it my turn?
    jne .retry                       ; no, wait
.acquired:
    ret

release:
    lock inc dword [myLock+4]
    ret

tl;博士; 一开始你不应该使用错误的工具(自旋锁);但是,如果您坚持使用错误的工具,那么至少要正确实施错误的工具... :-)

于 2012-12-23T13:11:37.853 回答
1

据我回忆,锁适用于一个单词(2 个字节)。在 486 中首次引入时就是这样写的。

如果您携带不同大小的锁,它实际上会生成相当于 2 个锁(双字的锁字 A 和字 B)。对于一个字节,它可能必须防止第二个字节的锁定,这有点相似到2把锁...

所以你的结果符合 CPU 优化。

于 2012-12-23T12:14:06.103 回答