内存alignment的目的

诚然,我不明白。 假设你有一个长度为1字节的内存字的内存。 为什么不能在一个不alignment的地址(即不能被4整除)的单个存储器访问中访问一个4字节长的variables,就像alignment地址一样?

这是许多底层处理器的限制。 通常可以通过执行4个低效的单字节提取而不是一个有效的单词提取来解决,但许多语言说明者认为只要取消它们并强制所有内容进行alignment就更容易了。

OP发现的链接中有更多的信息。

现代处理器上的存储器子系统仅限于以其字长的粒度和alignment方式访问存储器; 这是由于许多原因。

速度

现代处理器具有多级caching,数据必须通过; 支持单字节读操作会使内存子系统吞吐量与执行单元的吞吐量紧密相关(又称cpu-bound); 这一切都让人联想到DMA在硬盘驱动器中的许多相同原因超越了PIO模式 。

CPU 总是以字长(32位处理器上的4字节)进行读取,所以当你在一个支持它的处理器上进行不alignment的地址访问时,处理器将读取多个字。 CPU将读取您请求的地址跨越的每个内存单词。 这会导致访问所请求的数据所需的存储器事务处理的数量的2倍的放大。

正因为如此,读取两个字节比四个字节更容易。 例如,假设你在内存中有一个如下所示的结构体:

struct mystruct { char c; // one byte int i; // four bytes short s; // two bytes } 

在32位处理器上,它很可能如下所示alignment:

结构布局

处理器可以在一个事务中读取每个成员。

假设你有一个压缩的结构版本,也许从networking上打包传输效率; 它可能看起来像这样:

包装结构

读取第一个字节将是相同的。

当你要求处理器给你16位从0x0005时,它将不得不从0x0004读取一个字,然后左移1个字节把它放在一个16位寄存器; 一些额外的工作,但大多数可以在一个周期内处理。

当你要求0x0001的32位时,你会得到一个2X的放大。 处理器从0x0000读入结果寄存器,左移1个字节,再从0x0004读入临时寄存器,右移3个字节,然后与结果寄存器进行OR

范围

对于任何给定的地址空间,如果体系结构可以假定2个LSB总是0(例如,32位机器),那么它可以访问4倍的内存(2个保存的位可以表示4个不同的状态),或者相同的量内存2位的东西像标志。 从一个地址取2个LSB会给你一个4字节的alignment方式; 也被称为4字节的步幅 。 每次增加一个地址,它都会有效地递增第2位,而不是第0位,即最后2位总是继续为00

这甚至会影响系统的物理devise。 如果地址总线需要less于2位,则CPU上的引脚数可能会减less2个,电路板上的引脚数会减less2个。

primefaces性

CPU可以以primefaces方式对内存进行alignment操作,这意味着没有其他指令可以中断该操作。 这对许多无锁数据结构和其他并发范例的正确操作至关重要。

结论

处理器的存储器系统比这里描述的要复杂得多, 关于x86处理器如何实际处理内存的讨论可能会有所帮助

坚持内存alignment有很多好处,您可以在这篇IBM文章中阅读。

计算机的主要用途是转换数据。 现代存储器体系结构和技术经过了几十年的优化,以便以高度可靠的方式获取更多数据,进出数据,以及更快更快的执行单元。

奖金:caching

之前提到的另一种性能alignment方式是在高速caching行(例如在某些CPU上)64B上alignment。

有关利用caching可以获得多less性能的更多信息,请参阅处理器caching效果库 ; 从这个问题caching行大小

理解高速caching行对于某些types的程序优化可能是重要的。 例如,数据的alignment可以确定操作是否触摸一个或两个高速caching行。 正如我们在上面的例子中看到的那样,这很容易意味着在错位的情况下,操作会慢两倍。

你可以用一些处理器( nehalem可以这样做 ),但以前所有的内存访问都是在64位(或32位)行上alignment的,因为总线是64位宽的,所以你一次只能读取64位,并且在64位的alignment的“块”中取出这些文件要容易得多。

所以,如果你想得到一个字节,你取得了64位块,然后掩盖掉你不想要的位。 如果您的字节位于右端,那么方便快捷,但是如果它位于64位块的中间,则必须将不需要的位屏蔽掉,然后将数据移到正确的位置。 更糟糕的是,如果你需要一个2字节的variables,但是这个variables被分成了两个块,那么需要两倍的内存访问。

所以,每个人都认为内存很便宜,他们只是让编译器alignment处理器块大小的数据,这样代码就会以浪费的内存为代价,更快,更高效地运行。

基本上,原因是因为内存总线有一些特定的长度,远远小于内存大小。

因此,CPU读取片上L1caching,这通常是32KB。 但是连接L1高速caching到CPU的内存总线的高速caching行的宽度要小得多。 这将是128 的顺序。

所以:

 262,144 bits - size of memory 128 bits - size of bus 

未alignment的访问有时会重叠两个caching行,这将需要全新的caching读取以获取数据。 它甚至可能会错过所有的DRAM。

此外,CPU的某些部分将不得不站在这两个不同的高速caching行之中,每个高速caching行都有一个数据。 在一行中,它将处于非常高位,而在另一行中则是非常低位。

将会有专门的硬件完全集成到stream水线中,将alignment的对象移动到CPU数据总线的必要位上,但是这样的硬件可能缺less错位对象,因为使用这些晶体pipe加速正确优化可能更有意义程式。

在任何情况下,无论有多less专用硬件(假设和愚蠢地)专门用于修补未alignment的内存操作,有时需要的第二次内存读取都会减慢stream水线速度。

@Joshperry对这个问题给出了很好的答案。 除了他的回答之外,我还有一些数字显示了所描述的效果,特别是2倍放大。 这里有一个Google电子表格的链接,显示不同词汇排列的效果。 另外这里有一个链接到Github的testing代码的要点 。 testing代码是根据@joshperry引用的Jonathan Rentzsch写的文章改编的 。 testing是在一台四核2.8 GHz Intel Core i7 64位处理器和16GB RAM的Macbook Pro上运行的。

在这里输入图像描述

如果一个带字节寻址存储器的系统有一个32位宽的存储器总线,这意味着实际上有四个字节宽的存储器系统,它们都被连接到读或写相同的地址。 alignment的32位读取将需要在所有四个存储器系统中的相同地址中存储信息,因此所有系统都可以同时提供数据。 未alignment的32位读操作需要一些内存系统从一个地址返回数据,有些则从下一个更高的地址返回数据。 尽pipe有一些存储系统被优化以能够满足这种请求(除了地址之外,它们实际上还有一个“加一”的信号,使得它们使用比指定的地址高的地址),这样的特性增加了相当大的成本和一个内存系统的复杂性; 大多数商品存储系统不能同时返回不同的32位字的部分。

如果你有一个32位数据总线,连接到存储器的地址总线地址线将从A 2开始,所以在一个总线周期内只能访问32位alignment的地址。

因此,如果一个字跨越一个地址alignment边界 – 即16/32位数据的A 0或32位数据的A 1不为零,则需要两个总线周期来获取数据。

某些体系结构/指令集不支持未alignment的访问,并会在这种尝试中产生exception,所以编译器生成的未alignment的访问代码不仅需要额外的总线周期,而且还需要额外的指令,使其效率更低。

在PowerPC上,你可以从一个奇怪的地址加载一个整数没有问题。

Sparc和I86以及(我认为)当你尝试这个时,Itatnium会引发硬件exception。

一个32位负载与四个8位负载不会在大多数现代处理器上有很大的不同。 数据是否已经在caching中将会有更大的影响。