介绍与评测Intel HLE与RTM技术

HLE(即Hardware Lock Elision,硬件锁省略)以及RTM(即Restricted Transactional Memory,受限的事务性存储器)是Intel在x86微架构中所引入的两条指令集系统,它们均属于TSX(Transactional Synchronization Extensions,事务性同步扩展)指令集扩展。这套指令集扩展往往用于包含原子操作代码的临界区(Critical Section),通过将原子锁进行省略而使得多核多线程并行对此临界区的操作能进行提速。
下图比较详细地介绍了这两套指令集的执行逻辑以及使用方式。

coding transactions with TSX

这个图取自于Muttik等人的一份paper,各位可以参考此文:CREATING A SPIDER GOAT: USING TRANSACTIONAL MEMORY SUPPORT FOR SECURITY

下面我们将分别基于RTM和HLE来给出一些评测。各位要注意的是,尽管TSX在Intel Haswell微架构上就引入了,但那时候的实现尚不成熟,而且还有较严重的BUG。直到Skylake时代,部分处理器能正常时候该特性了。不过为了安全可靠起见,笔者建议各位在Kabylake或在此之后的处理器上运行以下代码。
由于之前关于“幽灵”、“熔断”等CPU高危漏洞的爆出,因此像更注重安全性的Mac已经把TSX全面屏蔽了,包括汇编器也直接不支持 XACQUIRE/XRELEASE 指令。因此笔者这里只能在装有Windows 10的联想笔记本上通过Visual Studio 2017 Community Edition进行测试。使用的处理器为Core i5 8250U。不过可惜的是,这款CPU没能支持TSX指令集扩展,我们只能稍作演示。
在Windows 10上如何通过Visual Studio 2017创建一个普通的C语言控制台项目,请参考这篇博文。我们这里就使用最基本的MSVC编译器即可。
下面先给出用于测试的test.asm汇编文件内容:

; test.asm

.code

    ; void cpu_pause(void)
    cpu_pause	proc public

    pause
    ret

    cpu_pause	endp

    ; void NormalAddTest(int *pArray, int count)
    NormalAddTest	proc public

    ; pArray => rcx
    ; count => edx
    mov     eax, 1

NormalAddTest_LOOP:

    add     [rcx], eax
    add     rcx, 4
    sub     edx, 1
    jne     NormalAddTest_LOOP

    ret

    NormalAddTest	endp

    ; void AtomicAddTest(int *pArray, int count)
    AtomicAddTest   proc public

    ; pArray => rcx
    ; count => edx
    mov     eax, 1

AtomicAddTest_LOOP:

    lock add    [rcx], eax
    add     rcx, 4
    sub     edx, 1
    jne     AtomicAddTest_LOOP

    ret

    AtomicAddTest   endp

    ; void HLEAtomicAddTest(int *pArray, int count)
    HLEAtomicAddTest	proc public

    ; pArray => rcx
    ; count => edx
    mov     eax, 1

HLEAtomicAddTest_LOOP:

    xacquire lock add    [rcx], eax
    add     rcx, 4
    sub     edx, 1
    jne     HLEAtomicAddTest_LOOP

    ret

    HLEAtomicAddTest    endp

    ; void FlagSetTest(volatile int *pFlag, int *pValue, int repeatCount)
    FlagSetTest         proc public

    ; pFlag => rcx
    ; pArray => rdx
    ; count => r8d

    mov     eax, 1
    xor     r9d, r9d
    jmp     FlagSetTest_LOOP

FlagSetTest_FAIL_HANDLER:
    pause

FlagSetTest_LOOP:

    xacquire lock bts   [rcx], r9d
    jc      FlagSetTest_FAIL_HANDLER

    add     [rdx], eax

    xrelease    mov     [rcx], r9d

    sub     r8d, 1
    jne     FlagSetTest_LOOP

    ret

    FlagSetTest         endp

END

下面给出main.c源文件内容:

// 你好,世界

#include <Windows.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

#define TEST_LOOP_COUNT     10

extern void cpu_pause(void);
extern void NormalAddTest(int *pArray, int count);
extern void AtomicAddTest(int *pArray, int count);
extern void HLEAtomicAddTest(int *pArray, int count);

extern void FlagSetTest(volatile int *pFlag, int *pValue, int repeatCount);

static volatile bool sIsComplete = false;

struct MyFuncCallParam
{
    void(*pFunc)(int*, int);
    int *pArray;
    int count;
};

static DWORD WINAPI TestThreadProc(LPVOID lpParam)
{
    struct MyFuncCallParam *callInfo = (struct MyFuncCallParam*)lpParam;
    void(*const pTestFunc)(int*, int) = callInfo->pFunc;
    int* const pBuffer = callInfo->pArray;
    const int count = callInfo->count;

    for (int i = 0; i < 100; i++)
        pTestFunc(pBuffer, count);

    sIsComplete = true;
    
    return 0;
}

static void TestAddFunction(void(*pTestFunc)(int*, int), int *pArray, int count)
{
    sIsComplete = false;

    struct MyFuncCallParam param = { pTestFunc, pArray, count };

    HANDLE hThread = CreateThread(NULL, 0, TestThreadProc, &param, 0, NULL);

    for (int i = 0; i < 100; i++)
        pTestFunc(pArray, count);

    while (!sIsComplete)
        cpu_pause();

    CloseHandle(hThread);
}


int main(int argc, const char * argv[])
{
    // 数据初始化
    const int count = 1024 * 1024;
    int *data = malloc(count * sizeof(data[0]));
    for (int i = 0; i < count; i++)
        data[i] = i;

    // 数据完整性测试

    TestAddFunction(AtomicAddTest, data, count);

    int errCount = 0;
    for (int i = 0; i < count; i++)
    {
        if (data[i] != i + 200)
            errCount++;
    }

    // 数据处理性能测试
    DWORD tBegin[TEST_LOOP_COUNT], tEnd[TEST_LOOP_COUNT];

    for (int i = 0; i < TEST_LOOP_COUNT; i++)
    {
        tBegin[i] = GetTickCount();

        TestAddFunction(AtomicAddTest, data, count);

        tEnd[i] = GetTickCount();
    }

    DWORD timeSpent = tEnd[0] - tBegin[0];
    for (int i = 1; i < TEST_LOOP_COUNT; i++)
    {
        const DWORD ts = tEnd[0] - tBegin[0];
        if (timeSpent > ts)
            timeSpent = ts;
    }

    printf("Time spent: %ums\n", timeSpent);
    printf("Error count: %d\n", errCount);

    volatile int flag = 0;
    data[0] = 0;

    FlagSetTest(&flag, data, 1);

    free(data);
}

各位在编译构建之后,最好在命令行下执行,这样能保证应用程序的执行不受其他剖析器等进程的影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值