在Intel 64 和 IA-32 架构优化参考手册中,Intel 给出了基于 PAUSE 的自旋等待循环的示例(“示例 11-4. 自旋等待循环和 PAUSE 指令”)。
但是,对于 UMONITOR/UMWAIT 指令,仅提及而没有任何示例(“支持用户级低功耗和低延迟自旋循环指令 UMWAIT/UMONITOR 和 TPAUSE”和“UMWAIT/UMONITOR/TPAUSE 指令”)在用户级旋转循环中实现节能”)。这些新指令随英特尔于 2020 年 9 月在 Elkhart Lake 平台中发布的 Tremont 微架构一起推出。
您能否给出一个关于汇编语言的SpinWaitAndAquireLock和ReleaseLock函数的工作示例,它们使用字节值作为同步变量(在64位或32位寻址模式下),以便该字节只能处于两种状态之一,即仅包含两个值:
cLockByteLocked
(例如,0FFh
)和cLockByteAvailable
(例如,0
)!?我可以使用 PAUSE 指令作为参考示例来给出这样的函数,可以通过 nasm -f win64
: 进行编译
section .text
; This code exposes two global functions: (1) SpinWaitAndAquireLock; (2) ReleaseLock.
cLockByteLocked equ 0FFh
cLockByteAvailable equ 0
; wait for the byte to not be locked and lock it
global SpinWaitAndAquireLock
; release lock obtained previously by SpinWaitAndAquireLock
global ReleaseLock
SpinWaitAndAquireLock:
; input: rcx - address of the synchronization variable lock byte
mov eax, cLockByteLocked ; load the value that denotes "locked" into al; use eax to clear remaining bits to avoid false dependency on existing eax value, bits 32-63 of rax are also cleared
jmp @FirstCompare
@NormalLoadLoop:
pause
@FirstCompare:
; use the "test, test-and-set" technique
cmp [rcx], al ; try normal load first instead of the locked load - "test" is a first part of "test, test-and-set"
je @NormalLoadLoop ; for static branch prediction, jump backward means "likely"
lock xchg [rcx], al ; since normal load shows the synchronization variable is available, try locked load and store ("test-and-set")
cmp al, cLockByteLocked
je @NormalLoadLoop
ret
ReleaseLock:
; input: rcx - address of the synchronization variable lock byte
mov byte [rcx], cLockByteAvailable ; just use normal store
ret
这是一个关于汇编语言的 SpinWaitAndAquireLock 和 ReleaseLock 函数的工作示例,它们在 64 位模式下使用字节值作为同步变量。此示例可以由
nasm
使用 -f win64
参数进行编译。它改编自https://github.com/maximmasiutin/FastMM4-AVX/blob/master/FastMM4.pas
本示例中的函数使用 Microsoft 的 Win64“x64 ABI”调用约定,即该示例无需修改即可在 Windows 下运行。在此调用约定中,第一个参数在
rcx
寄存器中传递。如果您希望在 Linux 下使用此示例,其中使用“System V AMD64 ABI”调用约定,并且第一个参数在 rdi
寄存器中传递,请将 `rcx' 替换为 'rdi'。
当程序使用的虚拟线程数量超过CPU提供的物理线程数量时,本例中的umonitor/umwait会带来显着的好处;否则没有任何好处。在 FastMM4-AVX 中,对于具有 64 个逻辑线程的 12 线程 CPU 上的 Linux,使用 umonitor/umwait 实现的程序比“暂停”之一快 6(!) 倍,基准代码位于 Tests/Benchmarks/Realloc FastMM 存储库中的 .dpr。
; wait for the byte to not be locked and lock it
global SpinWaitAndAquireLock
; release lock obtained previously by SpinWaitAndAquireLock
global ReleaseLock
cLockByteLocked equ 109
cLockByteAvailable equ 0
cUMWaitTime equ 7000000
; input: rcx - address of the synchronization variable lock byte
jmp @FirstLockMonitor
@DidntLockUmonitor:
mov eax, cLockByteLocked
cmp [rcx], al
jne @TryXchgAfterUmonitor
@FirstLockMonitor:
umonitor rcx
cmp [rcx], al
jne @TryXchgAfterUmonitor
mov eax, cUMWaitTime
xor rdx, rdx
; bit[0] = 0 C0.2 Slower Larger Improves performance of the other SMT thread(s) on the same core.
; bit[0] = 1 C0.1 Faster Smaller NA
xor r9, r9
umwait r9d
@TryXchgAfterUmonitor:
mov eax, cLockByteLocked
lock xchg [rcx], al
cmp al, cLockByteLocked
je @DidntLockUmonitor
; Locked after umonitor
ret
ReleaseLock:
; input: rcx - address of the synchronization variable lock byte
mov byte [rcx], cLockByteAvailable ; just use normal store
ret