如何使用 XACQUIRE、XRELEASE Hardware Lock Elision (HLE) 前缀提示?

Mik*_*keF 4 x86 assembly x86-64 intel cpu-architecture

只是为了学习这一点,我试图掌握如何使用HLE 前缀 XACQUIREXRELEASE. 阅读英特尔文档后,我的理解是,在执行带有XACQUIRE前缀的指令后,CPU 进入某种写锁,直到带有XRELEASE前缀的指令。所以我写了下面的测试代码,看看我是否正确。嗯,还有一些我不明白的地方,因为我的代码示例失败了。

那么有人可以告诉我这些 HLE 前缀遗漏了什么吗?

两次失败:

  1. xtest指令报告未启用 HLE,并且

  2. 因为我假设的“mutex-ed”代码不作为互斥体运行,所以它无法并发。

接下来是Windows C++项目,用VS 2017编译的x64 .asm文件如下:

.code

testCPUID PROC
    push rbx

    ; CPUID.07h.EBX.HLE[bit 4]==1

    mov eax, 7h
    xor ecx, ecx
    cpuid
    and rbx, 1 shl 4

    mov rax, rbx
    pop rbx
    ret
testCPUID ENDP



testHLEWrite PROC
    ; RCX = pointer to TST91 struct:
    ;       void* pPtrToNextWrite;
    ;       int nNextValue;
    ;       void* pCutoffPtr;
    ;       void* pBeginPtr;

    xor edx, edx
    xacquire xchg [rcx], rdx        ; I'm assuming that this will work as a mutex ...

    xtest                           ; Sanity check to see if HLE got enabled?
    jnz lbl_00                      ; If HLE is on => ZF=0
    int 3                           ; we get here if HLE did not get enabled
lbl_00:

    ; Do some nonsensical stuff
    ; The idea is to write sequential values into a shared array
    ; to see if the lock above holds
    ; Format:
    ;       > --16 sequential bytes-- <

    mov r8d, dword ptr [rcx + 8]

    mov byte ptr [rdx], '>'
    inc rdx

    ; Write 16 sequential bytes

    mov rax, 10h
lbl_01:
    mov byte ptr [rdx], r8b
    inc r8
    inc rdx
    dec rax
    jnz lbl_01

    mov byte ptr [rdx], '<'
    inc rdx

    cmp rdx, [rcx + 10h]            ; check if reached the end of buffer
    jb lbl_02
    mov rdx, [rcx + 18h]            ; reset ptr to the beginning of buffer
lbl_02:

    mov dword ptr [rcx + 8], r8d
    xrelease mov [rcx], rdx         ; this will release the mutex

    ret
testHLEWrite ENDP





testHLEForCorrectness PROC
    ; RCX = pointer to TST91 struct:
    ;       void* pPtrToNextWrite;
    ;       int nNextValue;
    ;       void* pCutoffPtr;
    ;       void* pBeginPtr;

    xor edx, edx
    xacquire xchg [rcx], rdx        ; I'm assuming that this will work as a mutex ...

    xtest                           ; Sanity check to see if HLE got enabled?
    jnz lbl_00                      ; If HLE is on => ZF=0
    int 3                           ; we get here if HLE did not get enabled
lbl_00:

    mov r9, [rcx + 18h]

lbl_repeat:
    cmp r9, rdx
    jae lbl_out

    cmp byte ptr [r9], '>'
    jnz lbl_bad
    cmp byte ptr [r9 + 1 + 10h], '<'
    jnz lbl_bad

    mov r8b, byte ptr [r9 + 1]
    sub eax, eax
lbl_01:
    cmp [r9 + rax + 1], r8b
    jnz lbl_bad
    inc rax
    inc r8
    cmp rax, 10h
    jb lbl_01

    add r9, 2 + 10h
    jmp lbl_repeat

lbl_out:

    xrelease mov [rcx], rdx         ; this will release the mutex

    ret

lbl_bad:
    ; Verification failed
    int 3

testHLEForCorrectness ENDP

END
Run Code Online (Sandbox Code Playgroud)

这是从用户模式 ​​C++ 项目中调用它的方式:

#include <assert.h>
#include <Windows.h>

struct TST91{
    BYTE* pNextWrite;
    int nNextValue;
    BYTE* pCutoffPtr;
    BYTE* pBeginPtr;
};

extern "C" {
    BOOL testCPUID(void);
    void testHLEWrite(TST91* p);
    void testHLEForCorrectness(TST91* p);
};

DWORD WINAPI ThreadProc01(LPVOID lpParameter);

TST91* gpStruct = NULL;
BYTE* gpMem = NULL;             //Its size is 'gszcbMemSize' BYTEs
const size_t gszcbMemSize = 0x1000 * 8;

int main()
{
    if(testCPUID())
    {
        gpStruct = new TST91;
        gpMem = new BYTE[gszcbMemSize];

        gpStruct->pNextWrite = gpMem;
        gpStruct->nNextValue = 1;
        gpStruct->pBeginPtr = gpMem;
        gpStruct->pCutoffPtr = gpMem + gszcbMemSize - 0x100;

        for(int t = 0; t < 5; t++)
        {
            CloseThread(CreateThread(NULL, 0, 
                ThreadProc01, (VOID*)(1LL << t), 0, NULL));
        }

        _gettch();

        delete gpStruct;
        delete[] gpMem;
    }
    else
        _tprintf(L"Your CPU doesn't support HLE\n");

   return 0;
}

DWORD WINAPI ThreadProc01(LPVOID lpParameter)
{
    if(!SetThreadAffinityMask(GetCurrentThread(), (DWORD_PTR)lpParameter))
    {
        assert(NULL);
    }

    for(;;)
    {
        testHLEWrite(gpStruct);
        testHLEForCorrectness(gpStruct);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Mik*_*keF 5

你可以回答你自己的问题,不是吗?

反正。我想我明白了。我会尽量坚持使用简单的英语,或者按照我的理解方式进行。如果我发表了不正确的陈述,请随意编辑它。(顺便说一句,Hardware Lock Elision多么酷的名字。听起来像马特达蒙的电影。我什至不得不用谷歌词“省略”来理解它的意思......我仍然不记得它。)

所以这个 HLE 概念无非是提示 CPU 以lock更优化的方式处理前缀。在lock通过自身的前缀是有点“贵”为现代处理器能够以高效的方式执行。因此,当支持它的 CPU 看到 HLE 前缀时,它最初不会获取锁,但只有在发生读/写冲突时才会这样做。在这种情况下,CPU 将发出 HLE 中止,这反过来将需要稍后的常规锁定。

此外,对于XACQUIREisF2和 for XRELEASEis的 HLE 前缀F3只不过是老派REPNEREP前缀,当与lock不支持 HLE 的旧 CPU的-able 指令一起使用时,它们会被简单地忽略。这一切意味着使用 HLE 不需要检查CPUID其支持的说明,并且可以按原样安全地使用它们。较旧的 CPU 会忽略它们并将伴随的lock前缀视为锁,而较新的 CPU 会将它们视为优化提示。换句话说,如果您将它们添加到您自己的互斥锁、信号量实现中,那么使用这些XACQUIREXRELEASE前缀不会有任何伤害。

因此,话虽如此,我不得不重写我的原始测试代码示例(只是非常基本的互斥锁类型的相关并发部分)。

进入锁的ASM代码:

testHLEWrite PROC
    ; RCX = pointer to TST91 struct:
    ;       void* pPtrToNextWrite;
    ;       int nNextValue;
    ;       void* pCutoffPtr;
    ;       void* pBeginPtr;
    ;       size_t lock;          <-- new member

lbl_retry:
    xacquire lock bts qword ptr [rcx + 20h], 1      ; Try to acquire lock (use HLE hint prefix)
    jnc lbl_locked
    pause                       ; Will issue an implicit HLE abort
    jmp lbl_retry


lbl_locked:
Run Code Online (Sandbox Code Playgroud)

然后离开锁:

(请注意,XRELEASE前缀与前缀的不同之处lock在于它支持mov具有内存目标操作数的指令。)

    xrelease mov qword ptr [rcx + 20h], 0       ; Release the lock (use HLE prefix hint)

    ret
testHLEWrite ENDP
Run Code Online (Sandbox Code Playgroud)

此外,如果您想使用(Visual Studio 的)内在函数用 C 编写它:

//Some variable to hold the lock
volatile long lock = 0;
Run Code Online (Sandbox Code Playgroud)

然后是代码本身:

//Acquire the lock
while(_interlockedbittestandset_HLEAcquire((long *)&lock, 1))
{
    _mm_pause();
}
Run Code Online (Sandbox Code Playgroud)

进而:

//Leave the lock
_Store_HLERelease(&lock, 0);
Run Code Online (Sandbox Code Playgroud)

最后,我想指出,我没有对带有和不带有 HLE 前缀的代码的性能进行任何计时/基准测试。因此,如果有人想这样做(并了解 HLE 概念的有效性),欢迎您这样做。我也会很高兴学习它。

  • 马特·达蒙 (Matt Damon) 的评论有点啰嗦,但我想我们可以让它通过,因为您确实有道理:) 无论如何,它可能来自这里 - http://pages.cs.wisc.edu/~rajwar/papers /micro01.pdf(尽管称为“事务性记忆”的一般概念早于此) (2认同)