> 文章列表 > 介绍与评测Intel HLE与RTM技术

介绍与评测Intel HLE与RTM技术

介绍与评测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 publicpauseretcpu_pause	endp; void NormalAddTest(int *pArray, int count)NormalAddTest	proc public; pArray => rcx; count => edxmov     eax, 1NormalAddTest_LOOP:add     [rcx], eaxadd     rcx, 4sub     edx, 1jne     NormalAddTest_LOOPretNormalAddTest	endp; void AtomicAddTest(int *pArray, int count)AtomicAddTest   proc public; pArray => rcx; count => edxmov     eax, 1AtomicAddTest_LOOP:lock add    [rcx], eaxadd     rcx, 4sub     edx, 1jne     AtomicAddTest_LOOPretAtomicAddTest   endp; void HLEAtomicAddTest(int *pArray, int count)HLEAtomicAddTest	proc public; pArray => rcx; count => edxmov     eax, 1HLEAtomicAddTest_LOOP:xacquire lock add    [rcx], eaxadd     rcx, 4sub     edx, 1jne     HLEAtomicAddTest_LOOPretHLEAtomicAddTest    endp; void FlagSetTest(volatile int *pFlag, int *pValue, int repeatCount)FlagSetTest         proc public; pFlag => rcx; pArray => rdx; count => r8dmov     eax, 1xor     r9d, r9djmp     FlagSetTest_LOOPFlagSetTest_FAIL_HANDLER:pauseFlagSetTest_LOOP:xacquire lock bts   [rcx], r9djc      FlagSetTest_FAIL_HANDLERadd     [rdx], eaxxrelease    mov     [rcx], r9dsub     r8d, 1jne     FlagSetTest_LOOPretFlagSetTest         endpEND

下面给出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     10extern 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);
}

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