不可否认,我不明白。假设您的内存中包含长度为1个字节的内存字。为什么不能在未对齐地址的单个存储器访问中访问一个4字节长的变量(即不能被4整除),因为对齐地址就是这种情况?
答案 0 :(得分:266)
现代处理器上的内存子系统仅限于以字大小和对齐方式访问内存;出于多种原因,情况就是如此。
现代处理器具有多级高速缓存,必须通过数据;支持单字节读取会使内存子系统吞吐量与执行单元吞吐量紧密绑定(也称为cpu-bound);这一切都让人想起PIO mode was surpassed by DMA在硬盘驱动器中的许多相同原因。
CPU 始终读取其字大小(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
移位。
对于任何给定的地址空间,如果架构可以假设2个LSB始终为0(例如,32位机器),那么它可以访问4倍的内存(2个保存的位可以代表4个不同的状态),或者对于类似标志的东西,2位的内存量相同。从地址中取出2个LSB将使您获得4字节对齐;也称为4个字节的stride。每次地址递增时,它实际上是递增第2位,而不是第0位,即最后2位将始终继续为00
。
这甚至可能影响系统的物理设计。如果地址总线需要2个较少的位,则CPU上的引脚数量可减少2个,电路板上的引脚数量减少2个。
CPU可以原子方式对齐对齐的存储器字,这意味着没有其他指令可以中断该操作。这对于许多lock-free data structures和其他concurrency范例的正确操作至关重要。
处理器的存储系统比这里描述的要复杂得多;关于how an x86 processor actually addresses memory的讨论可以提供帮助(许多处理器的工作方式类似)。
遵守内存对齐有许多好处,您可以在this IBM article阅读。
计算机的主要用途是转换数据。几十年来,现代存储器架构和技术已得到优化,以便以高度可靠的方式在更多和更快的执行单元之间获取更多数据。
我之前提到的另一个性能对齐是高速缓存行的对齐(例如,在某些CPU上)64B。
有关利用缓存可以获得多少性能的更多信息,请查看Gallery of Processor Cache Effects;来自question on cache-line sizes
了解缓存行对于某些类型的程序优化非常重要。例如,数据的对齐可以确定操作是否触摸一个或两个高速缓存行。正如我们在上面的示例中看到的,这很容易意味着在未对齐的情况下,操作将慢两倍。
答案 1 :(得分:54)
这是许多底层处理器的限制。它通常可以通过执行4个低效的单字节提取而不是一个有效的单词提取来解决,但是许多语言指定者认为仅仅取消它们并强制所有内容对齐会更容易。
OP发现的this link中有更多信息。
答案 2 :(得分:19)
你可以使用一些处理器(the nehalem can do this),但以前所有内存访问都是在64位(或32位)线上对齐的,因为总线是64位宽,你必须获取64位一次,在64位的对齐“块”中获取它们要容易得多。
因此,如果您想获得单个字节,则会获取64位块,然后屏蔽掉您不想要的位。如果您的字节位于右端,则简单快速,但如果它位于该64位块的中间,则必须屏蔽掉不需要的位,然后将数据移到正确的位置。更糟糕的是,如果你想要一个2字节的变量,但它被分成两个块,那么这需要双倍所需的内存访问。
因此,正如每个人都认为内存便宜一样,他们只是让编译器将数据与处理器的块大小对齐,这样就可以以浪费的内存为代价更快,更高效地运行代码。
答案 3 :(得分:3)
从根本上说,原因是因为内存总线的某些特定长度远远大于内存大小。
因此,CPU读取片上L1缓存,这些天通常为32KB。但是将L1高速缓存连接到CPU的内存总线将具有远小于高速缓存行大小的宽度。这将是128 位的数量级。
所以:
262,144 bits - size of memory
128 bits - size of bus
未对齐的访问偶尔会与两个缓存行重叠,这将需要全新的缓存读取才能获取数据。它甚至可能会错过DRAM。
此外,CPU的某些部分必须站在头上,将这两个不同的缓存行中的一个对象放在一起,每个缓存行都有一段数据。在一行中,它将处于非常高位的位,而另一行则是非常低位的位。
将完全集成到管道中的专用硬件处理将对齐的对象移动到CPU数据总线的必要位上,但是这些硬件可能缺少未对齐的对象,因为使用这些晶体管加速可能更有意义正确优化的程序。
在任何情况下,无论有多少专用硬件(假设和愚蠢地)用于修补未对齐的内存操作,有时需要的第二个内存读取会减慢管道速度。
答案 4 :(得分:3)
@joshperry对这个问题给出了很好的答案。除了他的回答,我还有一些数字以图形方式显示所描述的效果,特别是2X放大。这是指向Google spreadsheet的链接,显示不同字对齐的效果。 此外,这里还有一个Github gist的链接,其中包含测试代码。 测试代码改编自Jonathan Rentzsch撰写的the article,@ joshperry引用。测试是在带有四核2.8 GHz Intel Core i7 64位处理器和16GB RAM的Macbook Pro上运行的。
答案 5 :(得分:2)
如果具有字节可寻址存储器的系统具有32位宽的存储器总线,则意味着实际上有四个字节宽的存储器系统都连接到读取或写入相同的地址。对齐的32位读取将需要存储在所有四个存储器系统中的相同地址中的信息,因此所有系统都可以同时提供数据。未对齐的32位读取将需要一些存储器系统从一个地址返回数据,一些存储器系统从下一个更高的地址返回数据。虽然有一些存储系统经过优化,能够满足这些要求(除了它们的地址,它们实际上有一个“加一”信号,导致它们使用高于指定的地址)这样的功能增加了可观的成本和记忆系统的复杂性;大多数商品存储系统根本无法同时返回不同32位字的部分。
答案 6 :(得分:1)
如果您有32位数据总线,连接到存储器的地址总线地址线将从A 2 开始,因此在一个总线周期内只能访问32位对齐的地址。
因此,如果一个字跨越地址对齐边界 - 即16/32位数据的A 0 或32位数据的A 1 不为零,则两个总线周期需要获取数据。
某些体系结构/指令集不支持未对齐访问,并且会在此类尝试中生成异常,因此编译器生成的未对齐访问代码不仅需要额外的总线周期,还需要额外的指令,从而降低效率。
答案 7 :(得分:-1)
在PowerPC上,您可以从奇数地址加载一个整数,没有任何问题。
Sparc和I86以及(我认为)Itatnium会在您尝试此操作时引发硬件异常。
在大多数现代处理器上,一个32位负载与四个8位负载不会产生很大的不同。数据是否已经在缓存中将产生更大的影响。