当前位置: 首页 > article >正文

介绍与评测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);
}

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


http://www.kler.cn/a/14658.html

相关文章:

  • 如何用链表实现LRU缓存淘汰算法
  • 【android专题】学习android,第一天学习:软件和组件了解
  • AI | 浅谈AI技术以及其今后发展
  • 随机模型预测控制(SMPC)——考虑概率约束(Matlab代码实现)
  • 业内首批!安全狗入选Gartner《云原生应用保护平台市场指南》报告
  • 【小技巧】word文档编辑技巧(一)
  • SD卡恢复怎么做?内存卡数据恢复,3个方法!
  • 手把手教你安装telnet(离线方式+在线方式)
  • js特殊对象 - RegExp对象(正则表达式)
  • Linux文件操作基础及基本I/O函数使用
  • 如何雇佣一名全民开发者?
  • Mysql 索引
  • 无线电设备发射型号核准证(SRRC)
  • 数据预处理简单介绍,并给出具体的代码示例
  • Ceph入门到精通- storcli安装
  • spring-boot下Hikari、Druid的使用
  • 后台优化主要分为哪些?工作内容及流程是什么?
  • 如何从有故障的 SD 卡恢复文件
  • day35—选择题
  • NEFU-2023-算法设计与分析实验二动态规划算法设计