微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

x86_64 和 ARM64

如何解决x86_64 和 ARM64

我创建了一个简单的演示来说明未对齐的内存存储/加载在 x86_64 和 ARM64 架构上通常不是原子的。这个演示包含一个 C++ 程序,它创建了两个线程——第一个十亿次调用一个名为 store函数,第二个调用一个名为 load函数。该程序的源代码在这里

#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <thread>

extern "C" void store(void*);
extern "C" uint16_t load(void*);

alignas(64) char buf[65];
char* ptr;

static long n = 1'000'000'000L;

void f1()
{
  for (long i = 0; i < n; i++)
    store(ptr);
}

void f2()
{
  long v0x0000 = 0;
  long v0x0101 = 0;
  long v0x0100 = 0;
  long v0x0001 = 0;
  long other = 0;

  for (long i = 0; i < n; i++)
  {
    uint16_t a = load(ptr);

    if (a == 0x0000) v0x0000++;
    else if (a == 0x0101) v0x0101++;
    else if (a == 0x0100) v0x0100++;
    else if (a == 0x0001) v0x0001++;
    else other++;
  }

  std::cout << "0x0000: " << v0x0000 << std::endl;
  std::cout << "0x0101: " << v0x0101 << std::endl;
  std::cout << "0x0100: " << v0x0100 << std::endl;
  std::cout << "0x0001: " << v0x0001 << std::endl;
  std::cout << "other: " << other << std::endl;
}

int main(int arc,char* argv[])
{
  int offset = std::atoi(argv[1]);
  ptr = buf + offset;

  std::thread t1(f1);
  std::thread t2(f2);

  t1.join();
  t2.join();
}

storeload 函数在程序集源文件中分别定义。对于 x86_64 如下:

    .intel_Syntax noprefix 

    .global store
    .global load

    .text

store:
    mov eax,0
    mov WORD PTR [rdi],ax
    mov eax,0x0101
    mov WORD PTR [rdi],ax
    ret

load:
    movzx eax,WORD PTR [rdi]
    ret

并且,对于 ARM64 如下:

    .global store
    .global load

    .text

store:
    mov w1,0x0000
    strh w1,[x0]
    mov w1,0x0101
    strh w1,[x0]
    ret

load:
    ldrh w0,[x0]
    ret

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

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

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。