【问题标题】:why is data structure alignment important for performance?为什么数据结构对齐对性能很重要?
【发布时间】:2011-01-01 15:35:09
【问题描述】:

有人可以给我一个简短而合理的解释,说明为什么编译器会向数据结构添加填充以对齐其成员吗?我知道这样做是为了让 CPU 可以更有效地访问数据,但我不明白为什么会这样。

如果这只是 CPU 相关的,为什么在 Linux 中是双 4 字节对齐而在 Windows 中是 8 字节对齐?

【问题讨论】:

  • 有两个独立但相关的问题:数据对齐和数据结构填充
  • gcc 在 8 字节以及 x86 机器上对齐 dobules,与 Microsoft 的编译器相同。
  • 如果 CPU 以 4 字节的块读取数据,为什么双精度 8 字节对齐?那么 double 是 8 字节还是 4 字节对齐并不重要,不是吗?

标签: performance alignment memory-alignment abi


【解决方案1】:

对齐有助于 CPU 以有效的方式从内存中获取数据:减少缓存未命中/刷新、减少总线事务等。

某些内存类型(例如 RDRAM、DRAM 等)需要以结构化方式访问(对齐的“字”和“突发事务”,即一次处理多个字)才能产生有效的结果。这是由于许多原因,其中包括:

  1. 设置时间:内存设备访问内存位置所需的时间
  2. 总线仲裁开销,即许多设备可能想要访问内存设备

“Padding”用于纠正数据结构的对齐,以优化传输效率。


换句话说,访问“未对齐”的结构会降低整体性能。这种陷阱的一个很好的例子:假设一个数据结构是未对齐的,并且需要 CPU/内存控制器执行 2 个总线事务(而不是 1 个)以获取所述结构,因此性能会降低。

【讨论】:

  • 如果假设浮点数是 1 字节对齐的,会发生什么?
  • @Mat:那么,根据“浮动变量”最终在内存中分配的“位置”,访问这个“浮动变量”的效率会有所不同。
  • 但我是否理解正确,访问错误对齐的浮点数的性能不会比访问正确对齐的双精度数差?
  • @Mat: 否:访问未对齐的结构将导致降低整体效率,即降低性能。
【解决方案2】:

CPU 以 4 字节为一组从内存中获取数据(它实际上取决于硬件,它的 8 或某些类型硬件的其他值,但为了简单起见,我们坚持使用 4), 如果数据从可被 4 整除的地址开始,一切正常,CPU 会转到内存地址并加载数据。

现在假设数据从一个不能被 4 整除的地址开始,比如为了简单起见在地址 1,CPU 必须从地址 0 获取数据,然后应用某种算法在地址 0 处转储字节,以获得访问权限到字节 1 处的实际数据。这需要时间,因此会降低性能。因此将所有数据地址对齐会更有效。

【讨论】:

  • 不一定以 4 字节为一组:这高度依赖于 CPU 类型。
  • 这有点简化:在不能被 4 整除的内存位置有一个 BYTE 大小的值很好。在可以被 2 整除的内存位置有一个 WORD 大小的值也很好。
  • @Alon:它可能比您描述的要糟糕得多(“转储字节”):假设数据结构产生了一个需要额外总线事务的边界,那么性能就会超出预期。
  • @jldupont:我同意。为了更清楚地说明要点,我忽略了分页和缓存级别和大小
【解决方案3】:

缓存行是缓存的基本单元。通常为 16-64 字节或更多。

奔腾 IV:64 字节;奔腾 Pro/II:32 字节;奔腾 I:32 字节; 486:16 字节。

myrandomreader:
  ; ...
  ; ten instructions to generate next pseudo-random
  ; address in ESI from previous address
  ; ...
  MOV EAX, DS:[ESI]   ; X
  LOOP myrandomreader

对于跨越两个缓存线的内存读取:

(对于 L1 缓存未命中)处理器必须等待整个缓存行 1 从 L2->L1 读取到处理器中,然后才能请求第二个缓存行,从而导致短暂的执行停顿

(对于 L2 缓存未命中)处理器必须等待来自 L3 缓存(如果存在)或主内存的两次突发读取完成,而不是一次

处理器停止

  • 对于 64 字节缓存线,随机 4 字节读取将跨越缓存线边界的时间约为 5%,32 字节缓存线为 10%,16 字节缓存线为 20%。

  • 对于未对齐数据的某些指令,即使它位于高速缓存行内,也可能会产生额外的执行开销。这在英特尔网站上讨论了一些 SSE 说明。

  • 如果您自己定义结构,可能需要将所有 struct 中,这样可以减少填充开销,或者查看是否最好转为打包为特定结构打开或关闭。

  • 在 MIPS 和许多其他平台上,您没有选择,必须对齐 - 如果您不这样做,内核异常!!

  • 如果您在总线上执行 I/O 或使用原子操作(如原子递增/递减),或者如果您希望能够将代码移植到非英特尔,对齐也可能对您特别重要。

  • 仅在 Intel (!) 代码上,一种常见的做法是为网络和磁盘定义一组压缩结构,为内存定义另一组填充结构,并使用例程在这些格式之间转换数据(也考虑磁盘和网络格式的“字节顺序”)。

【讨论】:

    【解决方案4】:

    除了 jldupont 的回答之外,一些架构还具有 在字对齐边界上操作的加载和存储指令(用于读取/写入内存的指令) - 因此,要加载非内存中对齐的字需要两条加载指令、一条移位指令和一条屏蔽指令——效率要低得多!

    【讨论】:

    • 是否读取小于 4 字节的类型(bool、short 等)总是包含一个屏蔽操作,如果不是 4 字节对齐的还有一个移位指令?
    • @Mat:不一定是“移位指令”:在电路层面,芯片设计人员习惯于将这种类型的操作称为“字节交换器”。
    猜你喜欢
    • 2023-03-16
    • 2012-08-17
    • 2014-01-15
    • 1970-01-01
    • 2013-03-19
    • 2013-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多