C++编译优化这个地方坑有点多啊(后悔开这个坑了)
先放笔记吧,后面有时间再补这方面的东西
C++编译优化
- 编译优化,其实是编译时间,代码空间, 程序性能直接做权衡
- 汇编生成指令
- g++ -S test.cpp -O1 test.s
void test(bool cond, double *a, double *b, double *c, int len) {
for (int i = 0;i < len; i++)
if (cond)
c[i] += a[i] * b[i];
else
c[i] -= a[i] * b[i];
}
O0优化(默认)
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
movb %dil, %al
andb $1, %al
movb %al, -1(%rbp)
movq %rsi, -16(%rbp)
movq %rdx, -24(%rbp)
movq %rcx, -32(%rbp)
movl %r8d, -36(%rbp)
movl $0, -40(%rbp)
LBB0_1: ## =>This Inner Loop Header: Depth=1
movl -40(%rbp), %eax
cmpl -36(%rbp), %eax
jge LBB0_7
## %bb.2: ## in Loop: Header=BB0_1 Depth=1
testb $1, -1(%rbp)
je LBB0_4
## %bb.3: ## in Loop: Header=BB0_1 Depth=1
movq -16(%rbp), %rax
movslq -40(%rbp), %rcx
movsd (%rax,%rcx,8), %xmm0 ## xmm0 = mem[0],zero
movq -24(%rbp), %rax
movslq -40(%rbp), %rcx
mulsd (%rax,%rcx,8), %xmm0
movq -32(%rbp), %rax
movslq -40(%rbp), %rcx
addsd (%rax,%rcx,8), %xmm0
movsd %xmm0, (%rax,%rcx,8)
jmp LBB0_5
LBB0_4: ## in Loop: Header=BB0_1 Depth=1
movq -16(%rbp), %rax
movslq -40(%rbp), %rcx
movsd (%rax,%rcx,8), %xmm1 ## xmm1 = mem[0],zero
movq -24(%rbp), %rax
movslq -40(%rbp), %rcx
mulsd (%rax,%rcx,8), %xmm1
movq -32(%rbp), %rax
movslq -40(%rbp), %rcx
movsd (%rax,%rcx,8), %xmm0 ## xmm0 = mem[0],zero
subsd %xmm1, %xmm0
movsd %xmm0, (%rax,%rcx,8)
LBB0_5: ## in Loop: Header=BB0_1 Depth=1
jmp LBB0_6
LBB0_6: ## in Loop: Header=BB0_1 Depth=1
movl -40(%rbp), %eax
addl $1, %eax ## ++i
movl %eax, -40(%rbp)
jmp LBB0_1
LBB0_7:
popq %rbp
retq
.cfi_endproc
- 禁止绝大多数优化
- 最快的编译时间,最好的调试效果
- 特点
- 所有的变量都在内存中,会有大量的内存读写操作,运算结果才会放在寄存器上
- 编译器不会做任何优化,会尽可能的按照用户代码生成指令
- 调试方便,最大可能保证功能正确(在gdb调试中,可以直接向内存写值,但如果变量存放在寄存器上,就没有办法改变变量的值 )
O1优化
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
testl %r8d, %r8d
jle LBB0_5
## %bb.1:
movl %r8d, %r8d
xorl %eax, %eax
movapd LCPI0_0(%rip), %xmm0 ## xmm0 = [-0.0E+0,-0.0E+0]
jmp LBB0_2
.p2align 4, 0x90
LBB0_4: ## in Loop: Header=BB0_2 Depth=1
addsd (%rcx,%rax,8), %xmm1
movsd %xmm1, (%rcx,%rax,8)
addq $1, %rax ## ++i
cmpq %rax, %r8
je LBB0_5
LBB0_2: ## =>This Inner Loop Header: Depth=1
movsd (%rsi,%rax,8), %xmm1 ## xmm1 = mem[0],zero
mulsd (%rdx,%rax,8), %xmm1
testb %dil, %dil
jne LBB0_4
## %bb.3: ## in Loop: Header=BB0_2 Depth=1
xorpd %xmm0, %xmm1 ##取反操作
jmp LBB0_4
LBB0_5:
popq %rbp
retq
- 做些基础优化来降低codesize来提高运行性能
- 不会做影响编译时间比较大的优化
- 编译器会基于代码块优化,生成更加简洁的指令,但不会做复杂的跨代码块优化
- 优化点
- 循环变量i通过寄存器来计算,不再写内存
- 循环变量a[i]/b[i]/c[i]时的基址通过寄存器计算,而不是读取内存
- if (cond) 的逻辑不再是两大块代码,而是通过简单的取反
- 生成的代码紧凑性能比O0好
- 破坏了O0的调试能力,但是整体没有优化多少,所以比较鸡肋,clang默认是O1
O2优化
## %bb.0:
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
pushq %r14
pushq %rbx
.cfi_offset %rbx, -32
.cfi_offset %r14, -24
testl %r8d, %r8d
jle LBB0_21
## %bb.1:
movl %r8d, %r9d
cmpl $4, %r8d
jae LBB0_3
## %bb.2:
xorl %r8d, %r8d
jmp LBB0_10
LBB0_3:
leaq (%rcx,%r9,8), %r8
leaq (%rsi,%r9,8), %rax
cmpq %rcx, %rax
seta %r14b
leaq (%rdx,%r9,8), %rax
cmpq %rsi, %r8
seta %bl
cmpq %rcx, %rax
seta %r11b
cmpq %rdx, %r8
seta %r10b
xorl %r8d, %r8d
testb %bl, %r14b
jne LBB0_10
## %bb.4:
andb %r10b, %r11b
jne LBB0_10
## %bb.5:
movl %r9d, %r8d
andl $-4, %r8d
xorl %eax, %eax
movapd LCPI0_0(%rip), %xmm0 ## xmm0 = [-0.0E+0,-0.0E+0]
jmp LBB0_6
.p2align 4, 0x90
LBB0_8: ## in Loop: Header=BB0_6 Depth=1
movupd (%rcx,%rax,8), %xmm3
addpd %xmm1, %xmm3
movupd 16(%rcx,%rax,8), %xmm1
addpd %xmm2, %xmm1
movupd %xmm3, (%rcx,%rax,8)
movupd %xmm1, 16(%rcx,%rax,8)
addq $4, %rax
cmpq %rax, %r8
je LBB0_9
LBB0_6: ## =>This Inner Loop Header: Depth=1
movupd (%rsi,%rax,8), %xmm2
movupd 16(%rsi,%rax,8), %xmm3
movupd (%rdx,%rax,8), %xmm1
mulpd %xmm2, %xmm1
movupd 16(%rdx,%rax,8), %xmm2
mulpd %xmm3, %xmm2
testb %dil, %dil
jne LBB0_8
## %bb.7: ## in Loop: Header=BB0_6 Depth=1
xorpd %xmm0, %xmm1
xorpd %xmm0, %xmm2
jmp LBB0_8
LBB0_9:
cmpq %r9, %r8
je LBB0_21
LBB0_10:
movq %r8, %rax
notq %rax
testb $1, %r9b
je LBB0_14
## %bb.11:
movsd (%rsi,%r8,8), %xmm0 ## xmm0 = mem[0],zero
mulsd (%rdx,%r8,8), %xmm0
testb %dil, %dil
jne LBB0_13
## %bb.12:
xorpd LCPI0_0(%rip), %xmm0
LBB0_13:
addsd (%rcx,%r8,8), %xmm0
movsd %xmm0, (%rcx,%r8,8)
orq $1, %r8
LBB0_14:
addq %r9, %rax
jne LBB0_15
LBB0_21:
popq %rbx
popq %r14
popq %rbp
retq
LBB0_15:
movapd LCPI0_0(%rip), %xmm0 ## xmm0 = [-0.0E+0,-0.0E+0]
jmp LBB0_16
.p2align 4, 0x90
LBB0_20: ## in Loop: Header=BB0_16 Depth=1
addsd 8(%rcx,%r8,8), %xmm1
movsd %xmm1, 8(%rcx,%r8,8)
addq $2, %r8
cmpq %r8, %r9
je LBB0_21
LBB0_16: ## =>This Inner Loop Header: Depth=1
movsd (%rsi,%r8,8), %xmm1 ## xmm1 = mem[0],zero
mulsd (%rdx,%r8,8), %xmm1
testb %dil, %dil
jne LBB0_18
## %bb.17: ## in Loop: Header=BB0_16 Depth=1
xorpd %xmm0, %xmm1
LBB0_18: ## in Loop: Header=BB0_16 Depth=1
addsd (%rcx,%r8,8), %xmm1
movsd %xmm1, (%rcx,%r8,8)
movsd 8(%rsi,%r8,8), %xmm1 ## xmm1 = mem[0],zero
mulsd 8(%rdx,%r8,8), %xmm1
jne LBB0_20
## %bb.19: ## in Loop: Header=BB0_16 Depth=1
xorpd %xmm0, %xmm1
jmp LBB0_20
.cfi_endproc
- O1所有的优化的基础上,做所有能支持的优化,不再考虑代码空间和运行之间的tradeoff
- 会大量增加代码运行空间和编译时间
- i++优化成i += 2 (具体优化需要根据编译器来判定)
- 把数组向量化 (把标量操作转化成矩阵操作,循环展开)
- 向量化是非常关键的优化,把两个double放在xmm寄存器中同时计算
- 循环展开让一次循环迭代同时执行两个向量操作,通过器件冗余并行实现
- Clang在O2时就启用向量化,gcc在O3才启用
-O2 + -march=icelake-client优化
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
pushq %rbx
.cfi_offset %rbx, -24
testl %r8d, %r8d
jle LBB0_16
## %bb.1:
movl %r8d, %r9d
cmpl $16, %r8d
jae LBB0_3
## %bb.2:
xorl %eax, %eax
jmp LBB0_10
LBB0_3:
leaq (%rcx,%r9,8), %rax
leaq (%rsi,%r9,8), %r8
leaq (%rdx,%r9,8), %r10
cmpq %rcx, %r8
seta %r11b
cmpq %rsi, %rax
seta %bl
cmpq %rcx, %r10
seta %r8b
cmpq %rdx, %rax
seta %r10b
xorl %eax, %eax
testb %bl, %r11b
jne LBB0_10
## %bb.4:
andb %r10b, %r8b
jne LBB0_10
## %bb.5:
movl %r9d, %eax
andl $-16, %eax
xorl %r8d, %r8d
vbroadcastsd LCPI0_0(%rip), %ymm0 ## ymm0 = [-0.0E+0,-0.0E+0,-0.0E+0,-0.0E+0]
jmp LBB0_6
.p2align 4, 0x90
LBB0_8: ## in Loop: Header=BB0_6 Depth=1
vaddpd (%rcx,%r8,8), %ymm1, %ymm1
vaddpd 32(%rcx,%r8,8), %ymm2, %ymm2
vaddpd 64(%rcx,%r8,8), %ymm3, %ymm3
vaddpd 96(%rcx,%r8,8), %ymm4, %ymm4
vmovupd %ymm1, (%rcx,%r8,8)
vmovupd %ymm2, 32(%rcx,%r8,8)
vmovupd %ymm3, 64(%rcx,%r8,8)
vmovupd %ymm4, 96(%rcx,%r8,8)
addq $16, %r8
cmpq %r8, %rax
je LBB0_9
LBB0_6: ## =>This Inner Loop Header: Depth=1
vmovupd (%rsi,%r8,8), %ymm1
vmovupd 32(%rsi,%r8,8), %ymm2
vmovupd 64(%rsi,%r8,8), %ymm3
vmovupd 96(%rsi,%r8,8), %ymm4
vmulpd (%rdx,%r8,8), %ymm1, %ymm1
vmulpd 32(%rdx,%r8,8), %ymm2, %ymm2
vmulpd 64(%rdx,%r8,8), %ymm3, %ymm3
vmulpd 96(%rdx,%r8,8), %ymm4, %ymm4
testb %dil, %dil
jne LBB0_8
## %bb.7: ## in Loop: Header=BB0_6 Depth=1
vxorpd %ymm0, %ymm1, %ymm1
vxorpd %ymm0, %ymm2, %ymm2
vxorpd %ymm0, %ymm3, %ymm3
vxorpd %ymm0, %ymm4, %ymm4
jmp LBB0_8
LBB0_9:
cmpq %r9, %rax
je LBB0_16
LBB0_10:
movq %rax, %r8
notq %r8
addq %r9, %r8
movq %r9, %r10
andq $3, %r10
je LBB0_13
## %bb.11:
vmovapd LCPI0_1(%rip), %xmm0 ## xmm0 = [-0.0E+0,-0.0E+0]
.p2align 4, 0x90
LBB0_12: ## =>This Inner Loop Header: Depth=1
vmovsd (%rsi,%rax,8), %xmm1 ## xmm1 = mem[0],zero
vmulsd (%rdx,%rax,8), %xmm1, %xmm1
vxorpd %xmm0, %xmm1, %xmm2
kmovd %edi, %k1
vmovsd %xmm1, %xmm2, %xmm2 {%k1}
vaddsd (%rcx,%rax,8), %xmm2, %xmm1
vmovsd %xmm1, (%rcx,%rax,8)
incq %rax
decq %r10
jne LBB0_12
LBB0_13:
cmpq $3, %r8
jb LBB0_16
## %bb.14:
vmovapd LCPI0_1(%rip), %xmm0 ## xmm0 = [-0.0E+0,-0.0E+0]
.p2align 4, 0x90
LBB0_15: ## =>This Inner Loop Header: Depth=1
vmovsd (%rsi,%rax,8), %xmm1 ## xmm1 = mem[0],zero
vmulsd (%rdx,%rax,8), %xmm1, %xmm1
vxorpd %xmm0, %xmm1, %xmm2
kmovd %edi, %k1
vmovsd %xmm1, %xmm2, %xmm2 {%k1}
vaddsd (%rcx,%rax,8), %xmm2, %xmm1
vmovsd %xmm1, (%rcx,%rax,8)
vmovsd 8(%rsi,%rax,8), %xmm1 ## xmm1 = mem[0],zero
vmulsd 8(%rdx,%rax,8), %xmm1, %xmm1
vxorpd %xmm0, %xmm1, %xmm2
vmovsd %xmm1, %xmm2, %xmm2 {%k1}
vaddsd 8(%rcx,%rax,8), %xmm2, %xmm1
vmovsd %xmm1, 8(%rcx,%rax,8)
vmovsd 16(%rsi,%rax,8), %xmm1 ## xmm1 = mem[0],zero
vmulsd 16(%rdx,%rax,8), %xmm1, %xmm1
vxorpd %xmm0, %xmm1, %xmm2
vmovsd %xmm1, %xmm2, %xmm2 {%k1}
vaddsd 16(%rcx,%rax,8), %xmm2, %xmm1
vmovsd %xmm1, 16(%rcx,%rax,8)
vmovsd 24(%rsi,%rax,8), %xmm1 ## xmm1 = mem[0],zero
vmulsd 24(%rdx,%rax,8), %xmm1, %xmm1
vxorpd %xmm0, %xmm1, %xmm2
vmovsd %xmm1, %xmm2, %xmm2 {%k1}
vaddsd 24(%rcx,%rax,8), %xmm2, %xmm1
vmovsd %xmm1, 24(%rcx,%rax,8)
addq $4, %rax
cmpq %rax, %r9
jne LBB0_15
LBB0_16:
popq %rbx
popq %rbp
vzeroupper
retq
.cfi_endproc
-
march
- 为特定的cpu类型生成指令
- 生成的指令不一定能在其他平台上运行
- -march默认enable-mtune
-
mtune
- 为特定的cpu类型优化代码生成(一般是做调度方面的优化)
-
Icelake cpu拥有256位寄存器,可以同时计算4个double,同时支持四个运算单元同时计算,进一步优化i++的效率
向量化条件
- 向量化还需要一个额外条件, a a a与 b b b c c c数组的内存是不能 a l i a s alias alias
- 同时,向量化还需要scalar的版本来处理remainder部分
- a l i a s alias alias分析非常占用编译时间
- a l i a s alias alias分析是一个图的 n p np np问题
- 所有编译优化的基础都是 a l i a s alias alias分析
O3优化
- O2所有的优化基础上,牺牲编译时间来极尽可能来优化性能
- 主要是跨BB的loop变化,数据流分析等
- 由于O3会做很多激进的分析,大规模变换源代码,更容易暴露源代码逻辑的问题
- 代码逻辑从先循环再判断改为先判断再循环,判断走哪一个指令只需要判断一次