为什么需要内存对齐?

3

我知道这个问题已经被问了一千次,我已经阅读了每一个答案,但我仍然不明白。可能我的RAM模型存在一些根本性的错误,使我无法理解任何答案。

我从互联网上收集到了很多小信息,但我就是无法将它们联系起来。

以下是我目前所知道的:以IA-32架构为例,具有32位的字边界(边界=CPU可以从内存中读取的最大值?)。它将始终在其字边界处读取。

1)因此,无论我给出什么地址,它都将始终读取4个字节?如果我在地址x处有一个简单的字符,它会从该地址读取4个字节,然后进行一些奇怪的操作以获取一个字节吗?

2)如果是这样,那么一个字符串(一系列字符)n_chars * 4字节大吗?我很确定它不是这样的,但我应该如何解释“将始终读取其字边界”呢?

3)内存对齐似乎只涉及数据结构。为什么?内存的其余部分是否未对齐?我指的是物理、虚拟、内核空间等。

4)为什么我只能将32位值存储在可以被4整除的地址处?我的意思是,我知道它最终只会读取32位,但为什么它不能从奇数地址读取32位?这里有什么限制吗?

我很困惑,请帮帮我。


你似乎有一些误解。一旦你得到答案,所有的一切都会变得清晰明了! - fuz
1
不,不,不,还是不行。 - EOF
如果对齐的话,它可能会更好,但是x86处理器无论如何都能解决这个问题。也许通过加载两个部分并将它们粘在一起。或者将它们切成两个存储块。这可能需要更长的时间。 - Bo Persson
1
x86非常宽容,只要不是SIMD,它就可以接受你投入的任何东西。其他处理器则不然,Itanium就是一个明显的例子,当被迫从未对齐的地址读取时,会生成运行时错误。然而,这违反了语言的内存模型,在使用线程时很重要。如果32位int未对齐4,则有非零的可能性它跨越缓存行边界。这将强制处理器执行多个总线周期以将字节粘合在一起。另一个处理器可以观察到这一点,导致所谓的“撕裂”问题。 - Hans Passant
1
根据体系结构不同,现代x86单核心以64字节(RAM)块为单位(当然是对齐的),这意味着每次总是有64字节的内存从核心传输到上层缓存进行读写。这会产生“有趣”的副作用,如果你编写并发代码,其中几个核心的线程变量位于同一个64字节块中,即使它们没有共享一字节,性能也会受到影响,因为核心必须与每次写入自己字节的整个64字节块进行同步。这些和类似细节经常会导致意外的性能结果和很多困惑... - Ped7g
显示剩余2条评论
1个回答

7

在现代计算机中,内存是字节导向的。每个字节都有自己的地址,并可以从RAM中单独获取。为了您的程序,您可以假设获取一个字(word)就像以任意顺序获取组成它的字节,然后在您加载到的寄存器中将它们组装成一个字。

请注意,这是一个抽象概念。内存芯片通常被布线方式连接,一次会取出8个或更多字节。CPU具有一些电路来将所有这些东西从机器代码中抽象出来。但是,这种抽象是不完全的,这会导致许多效果:

  • 如果数据没有对齐到它的对齐要求,内存访问可能需要额外的周期,因为数据跨越了比必要的更多的字。通过充分对齐数据可以避免这种惩罚。
  • 当获取或写入对齐的数据时,在硬件中将其转换为单个取/存储。这样的取/存储是原子的,这是并发代码中的一个重要属性。当获取或写入未对齐的数据时,需要多个取/存储,操作不再是原子的。
  • 一些CPU根本不支持读/写非对齐内存,因为这简化了电路设计。这种限制在现代硬件中变得越来越少见。

那么现在,针对您的问题:

1) 因此,无论我给出什么地址,它都会读取4个字节吗?如果我在地址x处有一个简单的char,它会从该地址读取4个字节,然后进行某些奇怪的操作以获得一个字节吗?

也许。这取决于您使用的硬件。但是,是的,如果您请求一个字节,则会只获取一个字节。您不应该关心硬件读取多少字节才能为您提供那一个字节。

2) 如果是这样,那么一个字符串(字符序列)的大小为 n_chars * 4 字节吗?我很确定不是这样,但我应该如何解释“将始终读取其字边界”呢?

一个字符串通常是 n_chars 字节大小。当你从字符串中读取一个字符时,你得到一个字节。硬件可能会读取更多的字节来满足你的请求,但这不是你需要关心的事情。请注意,Windows 有时使用占用每个字符两个字节的 UTF-16 字符串,但这种趋势并没有真正流行起来。

3) 内存对齐似乎只涉及数据结构。为什么?内存在其他地方未对齐吗?我指的是物理、虚拟、内核空间等。

内存对齐在考虑 RAM 中的数据时很重要。无论内存是在内核还是用户进程中使用,都不重要。MMU 通常以保留对齐方式的方式映射内存,因此使用物理或虚拟内存并不重要。磁盘上的数据没有这些对齐要求,但由于所使用的存储器的扇区大小,可能适用于其他性能特征。

4) 为什么只能将32位值存储在可被4整除的地址上?我的意思是,我知道最终只会读取32位,但为什么不能从奇数地址读取32位?这里有什么限制呢?

如果您从奇数地址读取32位,则根据您的CPU和操作系统,可能会发生以下情况:

  1. 它可以正常工作
  2. 它可以正常工作,但速度会稍慢
  3. CPU会默默地忽略低2位,并从相应对齐的地址读取(这在现今很少见)
  4. CPU会抛出异常,如果您不处理它,程序就会崩溃
  5. CPU会抛出异常,操作系统会捕获该异常以为您模拟内存访问。

通常情况下,您不应假设会发生哪种情况。永远不要编写读取未对齐数据的代码。如果需要读取未对齐数据,请考虑逐个字节读取,然后手动重新组装所需的数据。


1
选项3.5 - CPU使用低2位作为对齐字地址的加载或存储的旋转计数。(旧版ARM)。 - Peter Cordes
1
@PeterCordes 真的吗?太奇怪了。我在哪里可以阅读相关资料? - fuz
https://medium.com/@iLevex/the-curious-case-of-unaligned-access-on-arm-5dd0ebe24965 描述了ARMv5及更早版本的这种效应。我错了,旋转只适用于未对齐的ldr,不适用于存储和ldm。(而对于ldrhldrd则是“不可预测的”)。 - Peter Cordes

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