有人可以给我一个简短而可信的解释,为什么编译器会添加填充以对齐数据结构中的成员?我知道这是为了使 CPU 更有效地访问数据,但我不明白为什么会这样。
如果这只涉及到 CPU,为什么在 Linux 中双倍精度值需要 4 字节对齐,在 Windows 中需要 8 字节对齐呢?
有人可以给我一个简短而可信的解释,为什么编译器会添加填充以对齐数据结构中的成员?我知道这是为了使 CPU 更有效地访问数据,但我不明白为什么会这样。
如果这只涉及到 CPU,为什么在 Linux 中双倍精度值需要 4 字节对齐,在 Windows 中需要 8 字节对齐呢?
对齐有助于CPU以高效的方式从内存中获取数据:减少缓存未命中、刷新,减少总线交易等。
某些内存类型(如RDRAM、DRAM等)需要以结构化方式访问(对齐的“字”和“突发事务”,即一次读取多个字),以产生高效结果。这是由于许多因素造成的,其中包括:
"填充"用于纠正数据结构的对齐,以优化传输效率。
换句话说,访问“未对齐”的结构将导致较低的整体性能。一个很好的例子是:假设一个数据结构未对齐且需要CPU / Memory Controller执行2个总线事务(而不是1个)以获取所述结构,则性能相应降低。
CPU以每4个字节为一组的方式从内存中获取数据(对于某些硬件,实际上取决于硬件可能是8或其他值,但为了简单起见,我们假设其为4), 如果数据的起始地址能够被4整除,则一切顺利,CPU会跳转到内存地址并加载数据。
现在假设数据起始地址不能够被4整除,为了简便起见,假设数据的起始地址为1,那么CPU必须从地址0处获取数据,然后应用某种算法来丢弃0地址处的字节,以访问字节1处的实际数据。这需要时间,因此会降低性能。因此,将所有数据地址对齐更加高效。
缓存行是缓存的基本单位。通常为16-64字节或更多。
Pentium IV: 64字节; Pentium Pro/II: 32字节; Pentium 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缓存未命中) 处理器必须等待从L2->L1读取整个缓存行1到处理器中,然后才能请求第二个缓存行,导致短暂的执行停顿。
(对于L2缓存未命中) 处理器必须等待两个突发读取从L3缓存(如果存在)或主存储器中完成,而不是一个。
处理器停顿
大约5%的时间,随机4字节读取将跨越64字节缓存行的边界,32字节的情况下为10%,16字节的情况下为20%。
即使在缓存行内,某些指令对于不对齐的数据可能会有额外的执行开销。这在Intel网站上谈论了一些SSE指令。
如果您自己定义结构,则可能有意义查看在struct
中列出所有<32位数据字段,以减少填充开销,或者审核是否更好地为特定结构打开或关闭打包。
在MIPS和许多其他平台上,您没有选择并且必须对齐-如果不这样做,会出现内核异常!
如果您正在总线上进行I/O或使用原子操作(例如原子递增/递减),或者希望能够将代码移植到非Intel平台,则对齐可能特别重要。
在仅限于Intel的代码中,一种常见的做法是为网络和磁盘定义一组紧凑的结构,为内存中的另一组填充,并拥有将数据在这些格式之间转换的例程(还考虑磁盘和网络格式的"字节序")。