【问题标题】:why 128bit variables should be aligned to 16Byte boundary为什么 128 位变量应该对齐到 16 字节边界
【发布时间】:2013-05-18 04:19:44
【问题描述】:

众所周知,X86 CPU 有一个 64 位的数据总线。我的理解是CPU无法访问任意地址。 CPU 可以访问的地址是其数据总线宽度的整数倍。为了性能,变量应该从这些地址开始(对齐)以避免额外的内存访问。对齐到 4Byte 边界的 32 位变量会自动对齐到 8Byte(64bit) 边界,对应 x86 64 位数据总线。但为什么编译器将 128 位变量与 16 字节边界对齐?不是 8Byte 边界?

谢谢

让我把事情说得更具体一些。编译器使用变量的长度来对齐它。例如,如果一个变量的长度为 256 位,Complier 会将其对齐到 32 字节的边界。我认为没有任何一种 CPU 具有那么长的数据总线。此外,普通的 DDR 内存一次只能传输 64 位数据,尽管有缓存,内存怎么会填满 CPU 更宽的数据总线呢?还是仅通过缓存?

【问题讨论】:

  • “我们知道,X86 CPU 有一个 64 位数据总线”——这不是真的。 x86 没有说明数据总线的大小。现代处理器实际上具有比这更大的数据总线宽度。
  • 处理器不从数据总线读取数据,而是从缓存中读取数据。需要 16 字节对齐以避免跨越缓存线边界。
  • @Mysticial 我认为目前最流行的 x86 CPU 具有 64 位数据总线,不是吗?
  • @iqapple 不。 Intel Core 2、Nehalem 和 Sandy Bridge 处理器具有 128 位宽的加载/存储端口。虽然不确定 AMD 的,但我认为自 K8 以来它们也有 128 位宽的加载/存储端口。高速缓存级别和内存之间的数据总线甚至很大。 (想想缓存行大小)
  • 32 位变量与 4 字节边界对齐,无需在任何 CPU 上将它们与 8 字节边界对齐。

标签: c++ c memory-management assembly x86


【解决方案1】:

有这么多不同的处理器型号,我将仅从理论上和一般术语来回答这个问题。

考虑一个 16 字节对象的数组,该数组的起始地址是 8 字节的倍数,但不是 16 字节的倍数。假设处理器有一个八字节总线,如问题中所示,即使某些处理器没有。但是,请注意,在数组中的某个点,其中一个对象必须跨越页边界:内存映射通常在以 4096 字节边界开始的 4096 字节页中工作。对于 8 字节对齐的数组,数组的某些元素将从一页的 4088 字节开始,一直到下一页的第 7 字节。

当程序尝试加载跨越页面边界的 16 字节对象时,它不能再执行单个虚拟到物理内存映射。它必须对前八个字节进行一次查找,对后八个字节进行另一次查找。如果加载/存储单元不是为此设计的,则指令需要特殊处理。处理器可能会中止执行指令的初始尝试,将其分成两个特殊的微指令,然后将它们发送回指令队列以执行。这会使指令延迟许多处理器周期。

此外,正如 Hans Passant 所指出的,对齐与缓存相互作用。每个处理器都有一个内存缓存,缓存通常被组织成 32 字节或 64 字节的“行”。如果加载一个 16 字节对齐的 16 字节对象,并且该对象在缓存中,那么缓存可以提供一个包含所需数据的缓存行。如果您从非 16 字节对齐的数组中加载 16 字节对象,则数组中的某些对象将跨越两个缓存行。加载这些对象时,必须从缓存中提取两行。这可能需要更长的时间。即使获得两条线不需要更长的时间,也许是因为处理器设计为每个周期提供两条高速缓存线,这可能会干扰程序正在执行的其他操作。通常,一个程序会从多个地方加载数据。如果负载是有效的,处理器可能能够同时执行两个。但是如果其中一个需要两条缓存线而不是正常的一条,那么它会阻止同时执行其他加载操作。

此外,一些指令明确要求对齐地址。处理器可能会更直接地发送这些指令,绕过一些修复没有对齐地址的操作的测试。当这些指令的地址被解析并发现未对齐时,处理器必须中止它们,因为修复操作已被绕过。

【讨论】:

  • 我知道你是对的,即使有些观点对我来说是深奥的。
  • IMO,这个答案的大部分虽然本身是正确的,但与“但为什么编译器将 128 位变量与 16 字节边界对齐?”的问题无关。这个问题的答案很简单,硬件要求它如此,编译器不这样做不是因为它更有效,而是因为任何其他方式都行不通。当你说“考虑一个 16 字节对象的数组,它的地址是 8 字节的倍数,但不是 16 字节的倍数。”,那是行不通的(因为 cpu 硬件不支持它)无论数组是否跨越页面边界。
  • 其实这取决于问题的“变量”是什么意思。我在想像 __m128i 这样的 128 个变量。如果是关于 struct foo {char x[128];}; 这样的事情,那么我同意 Eric。
  • @user2151446:硬件确实支持非对齐访问,通过诸如movups之类的指令。 C 实现支持未对齐的 16 字节 SIMD 对象是完全可行的。使用这种实现的程序通常会很慢,但它们会工作。因此,是否使用对齐地址的选择不是一种可能性或硬件支持,而是一种效率。
  • Eric 你夸大了数据对齐的重要性。一些得到现代 x86 处理器(即 Core i7)实验证据的支持,声明 读取或写入未对齐的内存操作数不会造成性能损失。更公平地说,在极端情况下,未对齐的数据确实会受到伤害,但通常在 x86 上并不重要。至于 128 位数据,这是问题的主题,说 SIMD 指令适用于对齐的数据,因为其中一条指令可以将数据复制到对齐的位置并不会改变 SSE2 硬件需要对齐数据的事实.
【解决方案2】:

一个原因是 X86 上的大多数 SSE2 指令要求数据是 128 位对齐的。这个设计决定是出于性能原因并避免过于复杂(因此又慢又大)的硬件。

【讨论】:

  • 我认为这可能是正确的。我陷入了一个循环,试图找出哪些编译器自动对齐用于矢量化 SIMD 计算的 __m128i 类型。
猜你喜欢
  • 2013-03-05
  • 2020-03-26
  • 2012-04-30
  • 2016-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-10
  • 1970-01-01
相关资源
最近更新 更多