内存对齐和访问粒度如何在汇编中工作?

How memory aligment and access granularity work in assembly?

我试图了解 CPU 内存对齐和 CPU 内存访问粒度是如何工作的,但我有点困惑,因为我找不到我的 CPU 以及两者如何相互作用以影响性能。所以有这个汇编代码 运行 x86-64 处理器:

start0:
  .byte 0   # at address 0x0
start1:
  .byte 1   # at address 0x1
start2:     # at address 0x2
  .quad 2
start3:     # at address 0x10 for example
  .quad 

movb start0, %al    # (1) aligned
movb start1, %al    # (2) unaligned
movq start2, %rax   # (3) unaligned
movq start3, %rax   # (4) aligned

这是不是意味着 (1) 会导致 CPU 从内存中只读取 1 个字节,或者 CPU 会读取 64 位内存并移动不需要的部分。

(2) 会导致 CPU 在地址 0x0 处读取,但它会读取多少是 64 位还是其他?

(3) 这会导致 CPU 执行两次内存读取,一次读取地址 0x00,另一次读取地址 0x8 并再次操作它们以获得正确的值吗?

(4) 将正常从地址 0x10 读取 8 个字节,对吗?

还有怎么知道我的CPU运行CPUID的访问粒度好像没有结果。我已经知道我的 CPU 的对齐方式是 8 字节对齐。

不,您自己尝试在可执行文件中使用该代码并使用调试器。在start3之前没有.p2align 3,就会错位。 2 + 8 = 0xa,而不是 0x10。汇编程序只是根据您的指令将字节发送到输出中,.quad 并不意味着对齐。


至于问题的主要部分:不,一个 1 字节的加载在逻辑上只访问那 1 个字节。例如如果您从 MMIO 地址加载,设备将只能看到 1 字节访问,而不是 qword 访问。

I already know the alignment of my CPU which is 8-bytes alignment.

除了 8 字节加载的性能优势外,这在任何意义上都是正确的。 x86-64 可以执行 1、2、4、8 或 16 字节的加载。 (对于 AVX 或 AVX-512、32 或 64 字节加载也是如此。)但它允许对任何这些大小进行未对齐加载。某些形式的 16 字节加载(如 SSE 内存操作数)需要 16 字节对齐,但低于 16 的则不需要。 (您可以在 EFLAGS 中设置一个对齐检查 (AC) 标志,但大多数时候它不是很有用,因为 memcpy 的编译器和 libc 实现自由使用未对齐访问。)即使在微体系结构上,现代 x86 硬件也确实可以高效地进行未对齐访问它的缓存。

对于可缓存的 RAM,CPU 内部所做的除了通过性能外是不可见的,但在逻辑上是等效的。

在现代 x86 CPUs 中,它实际上是从 RAM 加载的整个 64 字节缓存行。但我们或多或少可以证明,即使是硬件也在对缓存进行单字节访问,因为字节存储不会导致相邻负载的存储转发停顿,以及其他因素。参见

请注意,某些非 x86 CPUs 确实对单字节存储甚至加载的缓存访问速度较慢。 ()。 x86 CPUs 专为高效的未对齐和单字节访问而设计,因此软件会继续使用它们。但是在历史上不支持未对齐访问的 ISA 上,如 MIPS 或某种程度上的 ARM,软件通常会避免未对齐访问,因此硬件花费大量晶体管来提高速度的好处较少。

(此外,当前的 x86 设计针对的是一个用例,在该用例中需要花费更多的晶体管和功率来获得较小的速度增益,而大多数 ARM 设计则没有。还有 x86 CPU 尝试的因素加快现有二进制文件所做的事情,而不太希望让人们重新编译或重新设计软件以避免诸如未对齐访问之类的事情。总而言之,我认为现代 ARM / AArch64 具有合理的未对齐/字节访问,但不是零惩罚现代 x86 对不跨越缓存行边界的任何负载的处理方式。)


脚注 1:请注意,这适用于 asm;如果您使用 C 语言编写,则在编译器实际确定 asm 之前,语言/ABI 规则一直适用。请参阅 的示例,其中未对齐的 C 指针违反了编译器的假设,在针对 x86-64 进行编译时会导致问题。