现代处理器上的内存子系统仅限于以其字大小的粒度和对齐方式访问内存;造成这种情况的原因有很多。
Speed
现代处理器具有多级高速缓存,数据必须通过这些高速缓存进行提取;支持单字节读取将使内存子系统吞吐量与执行单元吞吐量紧密绑定(也称为 CPU 绑定);这一切都让人想起如何PIO模式被DMA超越出于许多与硬盘驱动器相同的原因。
The CPU always以其字大小读取(32 位处理器上为 4 字节),因此当您在支持它的处理器上进行未对齐地址访问时,处理器将读取多个字。 CPU 将读取您请求的地址跨越的每个内存字。这会导致访问所请求数据所需的内存事务数量最多放大 2 倍。
因此,读取两个字节很容易比读取四个字节慢。例如,假设内存中有一个如下所示的结构:
struct mystruct {
char c; // one byte
int i; // four bytes
short s; // two bytes
}
在 32 位处理器上,它很可能会如下所示进行对齐:
处理器可以在一个事务中读取这些成员中的每一个。
假设您有该结构的打包版本,可能来自为了传输效率而打包的网络;它可能看起来像这样:
读取第一个字节将是相同的。
当你要求处理器给你 0x0005 中的 16 位时,它必须从 0x0004 中读取一个字并左移 1 个字节以将其放入 16 位寄存器中;一些额外的工作,但大多数都可以在一个周期内完成。
当您从 0x0001 开始请求 32 位时,您将获得 2 倍的放大。处理器将从 0x0000 读入结果寄存器并左移 1 个字节,然后再次从 0x0004 读入临时寄存器,右移 3 个字节,然后OR
它与结果寄存器。
Range
对于任何给定的地址空间,如果架构可以假设 2 个 LSB 始终为 0(例如,32 位机器),那么它可以访问 4 倍以上的内存(2 个保存的位可以代表 4 个不同的状态),或者相同的数量具有 2 位的内存用于诸如标志之类的东西。从地址中取出 2 个 LSB 将得到 4 字节对齐;也称为stride4 字节。每次地址递增时,它实际上是递增位 2,而不是位 0,即,最后 2 位将始终继续是00
.
这甚至会影响系统的物理设计。如果地址总线需要减少 2 个位,则 CPU 上的引脚可以减少 2 个,电路板上的走线也可以减少 2 个。
原子性
CPU 可以原子地对内存的对齐字进行操作,这意味着没有其他指令可以中断该操作。这对于许多设备的正确操作至关重要无锁数据结构和别的并发性范式。
结论
处理器的内存系统比此处描述的要复杂得多,也更复杂;讨论x86 处理器实际上如何寻址内存可以提供帮助(许多处理器的工作原理类似)。
坚持内存对齐还有更多好处,您可以阅读:这篇 IBM 文章.
计算机的主要用途是转换数据。现代内存架构和技术经过数十年的优化,有助于以高度可靠的方式在更多、更快的执行单元之间输入、输出更多数据。
奖励:缓存
我之前提到的另一个性能对齐是缓存行(例如,在某些 CPU 上)64B 的对齐。
有关利用缓存可以获得多少性能的更多信息,请查看处理器缓存效果图库;由此关于缓存行大小的问题
了解缓存行对于某些类型的程序优化非常重要。例如,数据的对齐可以确定操作是否触及一个或两个高速缓存行。正如我们在上面的示例中看到的,这很容易意味着在未对齐的情况下,操作速度会慢两倍。