fuz*_*fuz 8 performance assembly x86-64 alignment
当我使用amd64或x86系统上的最新编译器编译C代码时,函数将对齐到16个字节的倍数.这种对齐在现代处理器上实际上有多重要?调用未对齐函数会产生巨大的性能损失吗?
我运行了以下microbenchmark(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
Run Code Online (Sandbox Code Playgroud)
使用以下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
Run Code Online (Sandbox Code Playgroud)
在自称为Intel(R)Core(TM)i7-2760QM CPU @ 2.40GHz的CPU上/proc/cpuinfo.偏移量对我没有影响,基准测试持续1.9秒运行.
另一方面,在另一个CPU报告自己为Intel(R)Core(TM)i7 CPU L 640 @ 2.13GHz的系统上,基准测试需要6.3秒,除非您有14或15的偏移,其中代码需要7.2秒.我认为这是因为该函数开始跨越多个缓存行.
TL;DR:缓存对齐很重要。您不想要不会执行的字节。
您至少希望避免在您将执行的第一个指令之前获取指令。由于这是一个微基准测试,您很可能看不到任何差异,但想象一下在一个完整的程序中,如果由于第一个字节未与缓存对齐而在一堆函数上有额外的缓存未命中-行,您最终必须为函数的最后 N 个字节获取一个新的缓存行(其中 N <= 您缓存但未使用的函数之前的字节数)。
英特尔的优化手册是这样说的:
3.4.1.5 代码对齐
仔细安排代码可以增强缓存和内存的局部性。可能的基本块序列应该在内存中连续排列。这可能涉及从序列中删除不太可能的代码,例如处理错误情况的代码。关于优化指令预取器,请参见第 3.7 节“预取”。
3-8 ?Assembly/Compiler Coding Rule 12.(M影响,H一般性)所有分支目标都应该是16字节对齐的。
汇编/编译器编码规则 13.(M 影响,H 通用性)如果不可能执行条件体,则应将其放置在程序的另一部分中。如果它极不可能被执行并且代码局部性是一个问题,则应将其放置在不同的代码页上。
它还有助于解释为什么您没有注意到程序中的任何差异。所有代码都被缓存一次并且永远不会离开缓存(当然是模上下文切换)。