x86_64 和 ARM64 上对齐与未对齐内存访问的不同运行时

Dan*_*ica 5 performance x86-64 cpu-architecture memory-alignment arm64

我创建了一个简单的演示来展示未对齐的内存存储/加载在 x86_64 和 ARM64 架构上通常不是原子的。该演示由一个 C++ 程序组成,该程序创建两个线程 \xe2\x80\x94,第一个线程 10 亿次调用名为 的函数store,第二个线程对名为 的函数执行相同的操作load。该程序的源代码在这里:

\n
#include <cstdint>\n#include <cstdlib>\n#include <iostream>\n#include <thread>\n\nextern "C" void store(void*);\nextern "C" uint16_t load(void*);\n\nalignas(64) char buf[65];\nchar* ptr;\n\nstatic long n = 1\'000\'000\'000L;\n\nvoid f1()\n{\n  for (long i = 0; i < n; i++)\n    store(ptr);\n}\n\nvoid f2()\n{\n  long v0x0000 = 0;\n  long v0x0101 = 0;\n  long v0x0100 = 0;\n  long v0x0001 = 0;\n  long other = 0;\n\n  for (long i = 0; i < n; i++)\n  {\n    uint16_t a = load(ptr);\n\n    if (a == 0x0000) v0x0000++;\n    else if (a == 0x0101) v0x0101++;\n    else if (a == 0x0100) v0x0100++;\n    else if (a == 0x0001) v0x0001++;\n    else other++;\n  }\n\n  std::cout << "0x0000: " << v0x0000 << std::endl;\n  std::cout << "0x0101: " << v0x0101 << std::endl;\n  std::cout << "0x0100: " << v0x0100 << std::endl;\n  std::cout << "0x0001: " << v0x0001 << std::endl;\n  std::cout << "other: " << other << std::endl;\n}\n\nint main(int arc, char* argv[])\n{\n  int offset = std::atoi(argv[1]);\n  ptr = buf + offset;\n\n  std::thread t1(f1);\n  std::thread t2(f2);\n\n  t1.join();\n  t2.join();\n}\n
Run Code Online (Sandbox Code Playgroud)\n

store函数load在汇编源文件中单独定义。对于x86_64如下:

\n
    .intel_syntax noprefix \n\n    .global store\n    .global load\n\n    .text\n\nstore:\n    mov eax, 0\n    mov WORD PTR [rdi], ax\n    mov eax, 0x0101\n    mov WORD PTR [rdi], ax\n    ret\n\nload:\n    movzx eax, WORD PTR [rdi]\n    ret\n
Run Code Online (Sandbox Code Playgroud)\n

并且,对于 ARM64 如下:

\n
    .global store\n    .global load\n\n    .text\n\nstore:\n    mov w1, 0x0000\n    strh w1, [x0]\n    mov w1, 0x0101\n    strh w1, [x0]\n    ret\n\nload:\n    ldrh w0, [x0]\n    ret\n
Run Code Online (Sandbox Code Playgroud)\n

当我运行该程序时,一切都按预期进行。当我传递偏移量 0 时,存储/加载会对齐,并且仅在读取线程中观察到值0x0000和。0x0101当我通过偏移量 63 时,存储/加载未对齐并跨越缓存行边界,并且值0x0100和 也0x0001被观察到。这对于两种架构都适用。

\n

但是,我注意到这些测试运行的执行时间存在很大差异。我观察到的一些典型时间:

\n
    \n
  • x86_64 + 偏移量 0(对齐):6.9 [s]
  • \n
  • x86_64 + 偏移 63(未对齐):28.3 [s]
  • \n
  • ARM64 + 偏移 0(对齐):6.8 [s]
  • \n
  • ARM64 + 偏移 63(未对齐):9.2 [s]
  • \n
\n

x86_64上,当两个缓存行涉及未对齐的情况时,运行时间会慢几倍。但在ARM64上,运行时间仅稍微慢一些我想知道两种架构之间的行为有何不同。(我对缓存一致性机制不太熟悉。)

\n

用于实验的特定处理器是Intel Xeon E5-2680 v3Cortex-A72。前者位于双套接字服务器中,但我将两个线程仅限制为单个套接字(通过tasksetnumactl)。后者在 Raspberry Pi 4 设备中。两个系统都运行 Linux,而且我使用 GCC 进行构建。

\n