当我在amd64或x86系统上使用最新编译器编译C代码时,函数将对齐到16个字节的倍数。这种对齐在现代处理器上实际上有多重要?调用未对齐函数会产生巨大的性能损失吗?
我运行了以下微基准测试(call.S
):
// benchmarking performance penalty of function alignment.
#include <sys/syscall.h>
#ifndef SKIP
# error "SKIP undefined"
#endif
#define COUNT 1073741824
.globl _start
.type _start,@function
_start: mov $COUNT,%rcx
0: call test
dec %rcx
jnz 0b
mov $SYS_exit,%rax
xor %edi,%edi
syscall
.size _start,.-_start
.align 16
.space SKIP
test: nop
rep
ret
.size test,.-test
使用以下shell脚本:
#!/bin/sh
for i in `seq 0 15` ; do
echo SKIP=$i
cc -c -DSKIP=$i call.S
ld -o call call.o
time -p ./call
done
在根据/proc/cpuinfo
将自身标识为 Intel(R)Core(TM)i7-2760QM CPU @ 2.40GHz 的CPU上。这个偏差对我来说没什么影响,基准测试经常持续1.9秒。
另一方面,在另一个CPU报告自身为 Intel(R)Core(TM)i7 CPU L 640 @ 2.13GHz 的系统上,基准测试需要6.3秒,除非是你有一个14或15的偏移量,代码需要7.2秒。我认为这是因为该函数开始跨越多个缓存行。
答案 0 :(得分:3)
TL; DR :缓存对齐很重要。你不想要你不会执行的字节。
至少,您希望避免在执行第一个指令之前获取指令。由于这是一个微基准测试,你很可能没有看到任何差异,但想象一下完整的程序,如果你在一堆函数上有额外的缓存缺失,因为第一个字节没有对齐到缓存行,你最终必须为函数的最后N个字节获取一个新的缓存行(其中N <=你缓存但没有使用的函数之前的字节数)。
3.4.1.5代码对齐
仔细安排代码可以增强缓存和内存位置。基本块的可能序列应该在存储器中连续布局。这可能涉及从序列中删除不太可能的代码,例如处理错误条件的代码。看到 关于优化指令预取器的第3.7节“预取”。
3-8汇编/编译器编码规则12.(M影响,H一般性)所有分支目标应为16字节对齐。
汇编/编译器编码规则13.(M影响,H一般性)如果条件的主体不可能被执行,则应将其放在程序的另一部分中。如果它不太可能被执行并且代码局部性是个问题,那么它应该放在不同的代码页上。
它还有助于解释为什么您没有注意到您的计划有任何差异。所有代码都缓存一次,永远不会离开缓存(当然是模数上下文切换)。