32位英特尔处理器上的内存对齐

17

英特尔的32位处理器,如奔腾系列,拥有64位宽数据总线,因此每次访问可以获取8个字节。基于这个事实,我假设这些处理器发出的物理地址在地址总线上总是8的倍数。

首先,这个结论是否正确?

其次,如果这个结论正确,那么数据结构成员应该按照8字节对齐。但是我看到有人在这些处理器上使用4字节对齐。

他们如何证明这样做是合理的?


1
我不知道这个问题的意思,但是我对它与编程有何关联以及它如何影响我感到很感兴趣。我在哪里可以阅读有关这种低级类型的基础介绍? - Rich Bradshaw
5
请看《程序员应该了解的内存知识》(What Every Programmer Should Know About Memory):http://people.redhat.com/drepper/cpumemory.pdf - Crashworks
1
从“请求的读取总是8的倍数”到“您的数据应始终在8字节边界上开始”,您如何理解?我不认为它们之间存在逻辑联系。只要数据没有跨越 8字节边界,我们就可以了,对吗? - jalf
5个回答

18
通常的经验法则(来自Intel和AMD的优化手册)是每种数据类型应该按其自身大小对齐。一个 int32 应该在32位边界上对齐,int64 在64位边界上对齐,等等。字符可以放在任何地方。
当然,另一个经验法则是“编译器已经被告知有关对齐要求”。你不需要担心它,因为编译器知道添加正确的填充和偏移量以允许高效地访问数据。
唯一的例外是在使用SIMD指令时,大多数编译器都需要手动确保对齐。
其次,如果正确的话,那么应该将数据结构成员对齐到8字节边界。但我看到一些人在这些处理器上使用4字节对齐。
我不明白这有什么影响。CPU可以简单地发出读取包含这4个字节的64位块的指令。这意味着它会在所请求的数据之前或之后获得4个额外的字节。但在这两种情况下,都只需进行一次读取。32位对齐32位宽度的数据可以确保它不会跨越64位边界。

不过,如果这4个字节跨越了一个64位块到下一个块,那么就不行。 - mP.
如果它在4字节边界上对齐,那会怎样发生? - jalf
5
我很难相信我错过了这个简单的推理。当你用4个字节就能达到相同效果时,为什么要在8字节对齐中浪费4个额外的字节呢?谢谢Jalf。你说的非常有道理。 - Frederick The Fool
@jalf,我还发布了另一个与对齐有关的问题(在这种情况下,是关于大小小于架构大小的单词),我不确定你回答中应用的推理是否适用于我的问题:http://stackoverflow.com/questions/22820576/reading-shorts-in-32-bits-architectures-for-example - ABu
编译器已经了解了对齐要求。但是编译器也必须遵守语言规范,对于C语言而言,这意味着不能重新排列成员。在许多平台上,重新排列结构成员可以提高性能,尽管在x86处理器上可能不会,但仍可能减少内存占用。 - yyny

8

物理总线宽度为64位,是8的倍数 --> 是的

但是,还有两个因素需要考虑:

  1. 一些x86指令集是按字节寻址的。有些是32位对齐的(这就是为什么你有4字节的东西)。但没有(核心)指令是64位对齐的。CPU可以处理不对齐的数据访问。
  2. 如果您关心性能,应该考虑缓存行而不是主内存。缓存行要宽得多。

我不理解。你同意像Pentium这样的处理器只在地址总线上放置8的倍数。然后你说4字节对齐是可以的。好吧,考虑地址0x000044444。虽然它是4字节对齐的,但处理器永远不会在地址线上发出这个地址,因为它不是8的倍数。因此,在这个地址处获取内存将需要两次获取。那么如何证明4字节对齐是合理的呢? - Frederick The Fool
3
为什么需要两次获取?只需请求从0x000044440到0x000044447的所有数据,因为我们只关心0x000044444-0x000044447,有什么问题吗? - jalf
为什么要谈论指令对齐,这毫无意义。使用NOP填充指令以达到某个边界并没有任何作用。 - mP.
x87指令可以执行64位的加载/存储操作(包括对64位整数数据的fild/fistp操作),而Pentium处理器已经集成了这个功能。此外,Pentium的lock cmpxchg8b指令也可以执行64位操作,但如果跨越缓存行边界,则速度非常慢。虽然没有指令需要32位对齐,但是它们受益于不跨越缓存行边界。 - Peter Cordes

2
他们这样做是有道理的,因为更改为8字节对齐将构成ABI更改,并且微小的性能提升不值得麻烦。
正如其他人已经说过的那样,缓存行很重要。所有实际内存总线上的访问都是以缓存行为单位(在x86上为64字节,如果我没记错的话)。请参阅已经提到的“每个程序员都需要了解的关于内存的知识”文档。因此,实际的内存流量是64字节对齐的。

1

对于随机访问,只要数据没有错位(例如跨越边界),我认为这并不重要;硬件可以使用简单的AND构造找到正确的地址和数据偏移量。当一个读取操作不足以获取一个值时,速度会变慢。这也是编译器通常将小值(字节等)放在一起的原因,因为它们不必在特定的偏移量上;short应该在偶数地址上,32位在4字节地址上,64位在8字节地址上。

请注意,如果涉及缓存和线性数据访问,则情况将有所不同。


1

你所提到的64位总线是用来供给缓存的。作为CPU,总是读写整个缓存行。缓存行的大小始终是8的倍数,并且其物理地址确实对齐于8字节偏移量。

从缓存到寄存器的传输不使用外部数据总线,因此该总线的宽度无关紧要。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接