【问题标题】:Why "any primitive object of K bytes must have an address that is a multiple of K"?为什么“任何 K 字节的原始对象的地址必须是 K 的倍数”?
【发布时间】:2018-10-27 02:20:03
【问题描述】:

计算机系统:程序员的视角说

x86-64 硬件无论对齐方式如何都可以正常工作 数据。但是,英特尔建议对齐数据以提高内存 系统性能。他们的对齐规则是基于原则 任何 K 字节的原始对象必须有一个地址 K 的倍数。 我们可以看到这条规则导致以下结果 对齐方式:

K Types
1 char
2 short
4 int, float
8 long, double, char *

为什么“任何 K 字节的原始对象的地址必须是 K 的倍数”?

“对齐”是如何定义的或者是什么意思?

在 x86-64 机器上,

  • 如果一个对象有 K 个字节(例如 K=2(例如 short)或 K=4(例如 int 或 float)),“任何 K 字节的原始对象的地址必须是倍数of K”表示这样的对象必须有一个是K的倍数的地址。但是对象不是对齐的,只要它的存储空间完全落在两个连续的8的倍数的地址之间,这是一个小于比对象的地址必须是 K 的倍数更严格的要求吗?

  • 如果对象的 K 小于 8 但不等于 1、2 或 4,“任何 K 字节的原始对象的地址必须是 K 的倍数”是否仍然适用?例如,如果 K=3、5、6 或 7?

在具有 32 位地址的 X86 机器上,

  • 什么是对齐规则,“任何 K 字节的原始对象的地址必须是 K 的倍数”是否仍然适用?

谢谢。

【问题讨论】:

  • 如果int 存储为(为简单起见)地址 0x3,您认为它是对齐的,因为它将包含 0x3 到 0x6(包括 0x3 到 0x6),而不跨越 0x0 或 0x8?那是怎么对齐的?如果您有一组所说的ints,您认为在这种情况下会发生什么。现在第二个是未对齐的(根据您的定义),因为它跨越 0x7 和 0x8。对齐的定义允许数组的一半索引对齐,而另一半未对齐,并且在生成它时不涉及奇怪的 hijink,这是一个奇怪的定义。
  • @ShadowRanger 谢谢。 (1)如果一个物体的K小于8但不等于2或4怎么办?例如,如果 K=6, 5? (2)在X86机器上,对齐规则是什么,“任何K字节的原始对象必须有一个K的倍数的地址”是否仍然适用?
  • @Ben 你知道大小为 3、5、6、7 的 primites 类型有哪些?
  • @Ben:对于#2,x86 非常乐意访问未对齐的内存,只是速度会变慢。据我所知,x86 上唯一需要的时间对齐是为了硬件原子支持等,否则如果您不喜欢不必要的慢代码,这只是一个好主意。但是,是的,除了不对齐的访问只是不可取的,而不是致命的,x86 在什么构成“对齐”数据方面遵循相同的规则。
  • @ShadowRanger 在 x86 上,当且仅当其地址是 4 字节(地址大小)或 8 字节(根据相同的对于 x86-64,规则“任何 K 字节的原始对象的地址必须是 K 的倍数”)?

标签: c memory-alignment


【解决方案1】:

因为它也被标记在 C 中;请注意,不仅架构做出这些决定,编译器也是如此。 C 编译器通常有自己的对齐规则,主要遵循架构的所需或首选对齐方式 - 特别是在优化 速度 时。而且编译器的要求是您最需要担心的,而不是架构要求。

即使处理器支持未对齐的访问,它也可能具有 C 编译器 可以利用的多字节对象的首选对齐方式。例如,允许编译器知道任何 int 将驻留,因此任何 int * 指针将始终指向 - 可被 4 整除的地址。

现在有人说,既然 x86-64 支持非对齐访问,他们可以创建一个 int * 指针,指向一个不能被 4 整除的地址,这样就可以正常工作了。

他们错了。

x86-64 指令集中一些指令需要对齐。 IE。 “无论对齐如何,都将正常工作”意味着这些指令也可以“根据规范,在给予未对齐访问时正确工作” - 它们引发异常,会终止您的进程。拥有这些的原因是,与可以处理未对齐数据的版本相比,它们可以更快并且需要更少的芯片来实现。

编译器知道确切什么时候允许使用这些指令!每当它看到 int * 被取消引用时,它知道它可以使用要求操作数以 4 个字节对齐的指令,如果它更有效的话。

如果 OP 遇到“在 x86-64 上应该没问题”的 C 代码问题,请参阅此问题:C undefined behavior. Strict aliasing rule, or incorrect alignment?


对于 x86-32,doubles 的对齐要求在 C 编译器中通常为 4,因为双精度值需要在堆栈上传递并且堆栈以 4 而不是 8 字节为增量增长。 p>


最后:

如果对象的 K 小于 8 但不等于 1、2 或 4,“任何 K 字节的原始对象的地址必须是 K 的倍数”是否仍然适用?例如,如果 K=3、5、6 或 7?

在 x86 中没有 K

C 标准的立场是对齐只能是 2 的幂,并且数组中没有间隙。因此,具有这种大小的对象需要向上填充到其对齐要求,或者其对齐要求必须为 1。

【讨论】:

    【解决方案2】:

    每个处理器型号的规则都不同。我将讨论一个假设的例子。我们可能有一个带有八字节总线接口的处理器。给定某个地址 X,处理器可以通过请求内存从其编号为 X/8 的存储单元中传送 8 个字节来从该地址加载 8 个字节。也就是说,内存没有任何方法来寻址单个字节。处理器只能请求某个地址为 8 的倍数的数据,而内存将在该地址发送整个 8 个字节。 (请记住,这是一个说明基本原理的假设示例。另外,我忽略了缓存。缓存有助于掩盖对齐问题的一些影响,因为可以在处理器内部的一级缓存中很大程度上管理不对齐。但是处理这仍然需要额外的硬件,如下所述。)

    假设我们想要字节 7、8、9 和 10 中的四字节对象。为了得到这个,处理器必须从内存中请求单元 0,它提供字节 0 到 7,它必须请求单元 1,它提供字节 8 到 15。因此,已经存在性能问题:我们必须使用两次总线传输来获得这个字,而这个字的大小只有一次传输的一半。这是低效的,如果我们只加载需要单次传输的对齐数据,总线只能完成一半的双次传输。

    接着,处理器拥有它需要的所有字节,从 0 到 15,因此它提取了字节 7 到 10,它们构成了我们想要的对象。但是,要做到这一点,它必须移动字节以将它们放入寄存器。理想情况下,如果没有人进行任何“未对齐”加载,则 4 字节对象将仅在 8 字节传输中的偏移量 0 和 4 处从总线进入,并且处理器只需要从这些偏移量连接到寄存器目标.

    但是,我们的处理器支持未对齐的负载,因此它具有额外的开关和电线,因此可以将数据分流到不同的路径,在那里它将被移动三个字节。请记住,来自两次传输的数据必须移动三个字节,然后拼接在一起。所以需要很多额外的电线和开关。两个 8 字节的传输是 128 位,所以这涉及到数百个额外的连接。

    嗯,很好,处理器有这些电线和开关,为什么不使用它们呢?为了使这个处理器更快,它支持同时进行多个加载和存储。一旦总线传输了一条数据,我们就想从总线上获取另一个数据,而来自第一个数据的数据仍在传输到寄存器的过程中。因此,实际上处理器的多个部分会为多个负载移动数据。由于我们预计未对齐的负载很少见,因此可能只有一个用于处理负载的部件具有额外的组件来处理未对齐的负载。其他的都处理对齐的负载。因此,如果您偶尔只有一个未对齐的负载,处理器会将其发送到该部分,并且性能影响是不明显的。但是,如果您连续执行许多未对齐的加载,它们都必须经过一个部分,因此它们最终会在队列中等待而不是并行运行,并且性能会下降。

    这只是为了负载。当您存储该四字节对象时,无法仅写入字节 7 到 10。由于总线和内存仅以八字节为单位工作,因此我们需要写入单元 0 和 1,其中也包含字节 0 到6 和字节 11 到 15。要实现存储,处理器必须:

    • 加载内存单元 0,提供字节 0 到 7。
    • 加载内存单元 1,提供字节 8 到 15。
    • 将四字节对象的第一个字节移动到字节 7。
    • 将对象的最后三个字节移动到字节 8 到 10。
    • 存储更改的内存单元 0。
    • 存储更改的内存单元 1。

    同样,这是对齐对象的两倍(加载一个内存单元,将字节移入,存储单元)。而且,除了操作时间之外,您还占用了处理器内部更多的资源——它在合并更改时必须使用两个内部寄存器来临时保存内存中的数据。

    实际上,它的工作量和资源量是其两倍以上,因为它还需要额外的电线和开关来将字节移位非标准数量。

    【讨论】:

      【解决方案3】:

      作为用于访问内存的介质的处理器总线通常是以位为单位的处理器大小。这意味着 32 位处理器通常以 32 位块访问内存,这意味着只需一次内存读取访问即可从内存中读取数据。

      相反,地址是面向字节的,因此双精度(8 字节)通常占用八个不同的连续内存。因此,要访问单个八字节数据(只有一个总线请求),数据必须从单个八字节字开始,并在我们进入下一个字之前完成。对于旧处理器,这是必不可少的,如果您请求的内存访问不是数据对齐的,则会引发异常。实际的处理器没有此限制,但请注意,如果您在非 8 的倍数地址中有例如 double,处理器将需要进行两次总线访问(这意味着开销)来获取数据凭记忆。

      出于这个原因(如果所有数据未对齐,您可以将执行某些代码所需的时间加倍甚至更多,而如果数据正确对齐所需的时间)处理器供应商会警告您对齐数据。

      现代处理器具有多个级别的缓存,这些缓存以一个缓存行(64 甚至更多字节)的块从主内存中读取,因此这不是问题。无论如何,无论如何都要对齐数据是个好主意,因为您需要在非高级处理器中运行代码。

      【讨论】:

        猜你喜欢
        • 2011-12-12
        • 1970-01-01
        • 2011-04-23
        • 2018-07-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-12-03
        相关资源
        最近更新 更多