美文网首页高性能计算相关
介绍与评测Intel HLE与RTM技术

介绍与评测Intel HLE与RTM技术

作者: zenny_chen | 来源:发表于2018-07-25 17:36 被阅读94次

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

    hello.jpg
    这个图取自于Muttik等人的一份paper,各位可以在笔者的网易网盘里下载到:(http://u.163.com/IDLSCd10 提取码: yUYJ7EtB)。
    下面我们将分别基于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);
    }
    

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

    相关文章

      网友评论

        本文标题:介绍与评测Intel HLE与RTM技术

        本文链接:https://www.haomeiwen.com/subject/cpcmmftx.html