为什么数据结构对齐对性能很重要?

Mat*_*Mat 27 alignment

有人能给我一个简短而合理的解释,说明为什么编译器为数据结构添加填充以便对齐其成员?我知道它已经完成,以便CPU可以更有效地访问数据,但我不明白为什么会这样.

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

jld*_*ont 16

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

需要以结构化方式(对齐的"字"和"突发事务",即一次多个字)访问一些存储器类型(例如RDRAM,DRAM等),以便产生有效的结果.这是由于其中的许多因素:

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

"填充"用于校正数据结构的对齐以优化传输效率.


换句话说,访问"错位"结构将产生较低的整体性能.这种陷阱的一个很好的例子:假设数据结构不对齐并且需要CPU /存储器控制器执行2个总线事务(而不是1)以便获取所述结构,因此性能因此降低.


Alo*_*lon 12

CPU以4个字节为一组从内存中取出数据(它实际上取决于硬件的8或某些类型硬件的其他值,但是让它坚持4以保持简单),如果数据在地址中开始,那么一切都很好可分为4,CPU进入存储器地址并加载数据.

现在假设数据开始于一个不可分割的地址4,为了简化地址1,CPU必须从地址0获取数据,然后应用一些算法将字节转储到0地址,以获得对实际的访问权限字节1处的数据.这需要时间,因此降低了性能.因此,使所有数据地址对齐更有效.

  • 我很简单;-) (3认同)
  • 不一定以 4 字节为一组:这高度依赖于 CPU 类型。 (2认同)
  • @Alon:它可能比你描绘的要糟糕得多("dumping bytes"):假设数据结构产生了一个需要额外总线事务的边界,那么性能就会消失. (2认同)
  • @jldupont:我同意.我忽略了分页和缓存级别和大小,以更清楚地说明主要观点 (2认同)

mar*_*inr 7

缓存行是缓存的基本单位.通常它是16-64字节或更多.

奔腾IV:64字节; Pentium 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
Run Code Online (Sandbox Code Playgroud)

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

(对于L1高速缓存未命中)处理器必须等待整个高速缓存行1从L2-> L1读入处理器才能请求第二高速缓存行,从而导致执行暂停

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

处理器停滞

  • 随机4字节读取将跨越高速缓存行边界约5%的时间用于64字节高速缓存行,10%用于32字节高速缓存,20%用于16字节高速缓存行.

  • 对于未对齐数据的某些指令,可能存在额外的执行开销,即使它在高速缓存行内.有关SSE指令,请参阅英特尔网站上的内容.

  • 如果您自己定义结构,那么将所有<32位数据字段列在一起struct以便减少填充开销或者检查是否更好地为特定结构打开或关闭打包可能是有意义的.

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

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

  • 在Intel(!)代码上,通常的做法是为网络和磁盘定义一组压缩结构,为内存中定义另一个填充集,并在这些格式之间转换数据(也考虑"endianness")磁盘和网络格式).