有人能给我一个简短而合理的解释,说明为什么编译器为数据结构添加填充以便对齐其成员?我知道这样做是为了让CPU可以更有效地访问数据,但我不明白为什么会这样。
如果这只与CPU有关,为什么在Linux中对齐4字节,在Windows中对齐8字节呢?
答案 0 :(得分:16)
对齐有助于CPU以有效的方式从内存中获取数据:更少的缓存未命中/刷新,更少的总线事务等。
某些存储器类型(例如RDRAM,DRAM等)需要以结构化方式(对齐的“字”和“突发事务”,即一次多个字)进行访问,以便产生有效的结果。这是由于其中的许多因素:“填充”用于纠正数据结构的对齐,以优化传输效率。
换句话说,访问“错位”结构将降低整体性能。这种陷阱的一个很好的例子:假设数据结构不对齐并且需要CPU /内存控制器执行2个总线事务(而不是1)以获取所述结构,因此性能因此降低。
答案 1 :(得分:12)
CPU以4个字节为一组从内存中获取数据(实际上,它取决于硬件的8或某些类型硬件的其他值,但让我们坚持4以保持简单), 如果数据以可分为4的地址开始,那么一切都很好,CPU进入存储器地址并加载数据。
现在假设数据开始于一个地址不能分为4,为了简化地址1,CPU必须从地址0获取数据,然后应用一些算法将字节转储到0地址,以获得访问权限到字节1的实际数据。这需要时间,因此降低了性能。因此,使所有数据地址对齐更有效。
答案 2 :(得分: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
对于跨越两个缓存行的内存读取:
(对于L1缓存未命中)处理器必须等待整个缓存行1从L2-> L1读入处理器才能请求第二缓存行,从而导致执行暂停
(对于L2缓存未命中)处理器必须等待来自L3缓存(如果存在)或主存储器的两次突发读取才能完成而不是一次
处理器停止
随机4字节读取将跨越高速缓存行边界大约5%的时间用于64字节高速缓存行,10%用于32字节高速缓存,20%用于16字节高速缓存行。
对于未对齐数据的某些指令,可能会有额外的执行开销,即使它位于高速缓存行内。英特尔网站上讨论了一些SSE指令。
如果您自己定义结构,那么在struct
中查看列出所有< 32位数据字段可能是有意义的,这样可以减少填充开销,或者检查是否更好打开或关闭特定结构的包装。
在MIPS和许多其他平台上,你没有得到选择,必须对齐 - 内核例外,如果你没有!
如果您在总线上进行I / O或使用原子操作(如原子递增/递减)或希望能够将代码移植到非Intel,则对齐也可能对您特别重要。
在仅限英特尔(!)的代码中,通常的做法是为网络和磁盘定义一组压缩结构,为内存中定义另一个填充集,并在这些格式之间转换数据(也是考虑磁盘和网络格式的“endianness”。
答案 3 :(得分:3)
除了jldupont的答案之外,一些架构还有加载和存储指令(那些用于读/写内存的指令)仅在字对齐边界上操作 - 因此,加载非从存储器中对齐的字将需要两个加载指令,一个移位指令,然后是一个掩码指令 - 效率低得多!