GCC如何优化一个在循环内部递增的未使用的variables?
我写了这个简单的C程序:
int main() { int i; int count = 0; for(i = 0; i < 2000000000; i++){ count = count + 1; } }
我想看看gcc编译器如何优化这个循环(显然加120亿次应该是“新增20亿次”)。 所以:
gcc test.c然后在a.out
给出:
real 0m7.717s user 0m7.710s sys 0m0.000s
$ gcc -O2 test.c然后time on
a.out time on
执行time on
:
real 0m0.003s user 0m0.000s sys 0m0.000s
然后我用gcc -S
拆卸了两个。 第一个看起来很清楚:
.file "test.c" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 movq %rsp, %rbp .cfi_offset 6, -16 .cfi_def_cfa_register 6 movl $0, -8(%rbp) movl $0, -4(%rbp) jmp .L2 .L3: addl $1, -8(%rbp) addl $1, -4(%rbp) .L2: cmpl $1999999999, -4(%rbp) jle .L3 leave .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2" .section .note.GNU-stack,"",@progbits
L3增加,L2比较-4(%rbp)
与1999999999
,如果i < 2000000000
,则循环到L3。
现在优化一个:
.file "test.c" .text .p2align 4,,15 .globl main .type main, @function main: .LFB0: .cfi_startproc rep ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2" .section .note.GNU-stack,"",@progbits
我根本无法理解那里发生了什么事! 我对assembly知之甚less,但我期望的是类似的东西
addl $2000000000, -8(%rbp)
我甚至用gcc -c -g -Wa,-a,-ad -O2 test.c试着看到C代码和它被转换的程序集一起,但是结果并没有更清楚地说明前一个。
有人可以简单解释一下
- gcc -S -O2输出。
- 如果循环是按我的预期进行优化的(一个和数而不是多和)?
编译器甚至比这更聪明。 🙂
事实上,它意识到你没有使用循环的结果。 所以它完全拿出了整个循环!
这被称为死码消除 。
更好的testing是打印结果:
#include <stdio.h> int main(void) { int i; int count = 0; for(i = 0; i < 2000000000; i++){ count = count + 1; } // Print result to prevent Dead Code Elimination printf("%d\n", count); }
编辑:我已经添加了所需的#include <stdio.h>
; MSVC程序集列表对应于没有#include
的版本,但应该是相同的。
我现在没有GCC在我面前,因为我被引导到Windows。 但是这里是在MSVC上用printf()
反汇编的版本:
编辑:我有错误的组装输出。 这是正确的。
; 57 : int main(){ $LN8: sub rsp, 40 ; 00000028H ; 58 : ; 59 : ; 60 : int i; int count = 0; ; 61 : for(i = 0; i < 2000000000; i++){ ; 62 : count = count + 1; ; 63 : } ; 64 : ; 65 : // Print result to prevent Dead Code Elimination ; 66 : printf("%d\n",count); lea rcx, OFFSET FLAT:??_C@_03PMGGPEJJ@?$CFd?6?$AA@ mov edx, 2000000000 ; 77359400H call QWORD PTR __imp_printf ; 67 : ; 68 : ; 69 : ; 70 : ; 71 : return 0; xor eax, eax ; 72 : } add rsp, 40 ; 00000028H ret 0
所以是的,Visual Studio做这个优化。 我会认为GCC可能也是。
是的,GCC执行类似的优化。 下面是与gcc -S -O2 test.c
(gcc 4.5.2,Ubuntu 11.10,x86)相同程序的汇编列表:
.file "test.c" .section .rodata.str1.1,"aMS",@progbits,1 .LC0: .string "%d\n" .text .p2align 4,,15 .globl main .type main, @function main: pushl %ebp movl %esp, %ebp andl $-16, %esp subl $16, %esp movl $2000000000, 8(%esp) movl $.LC0, 4(%esp) movl $1, (%esp) call __printf_chk leave ret .size main, .-main .ident "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2" .section .note.GNU-stack,"",@progbits
编译器可以使用一些工具来使代码更高效或更“高效”:
-
如果从不使用计算结果,则可以省略执行计算的代码(如果计算对
volatile
值起作用,则必须仍然读取这些值,但读取的结果可能会被忽略)。 如果没有使用它的计算结果,那么执行这些代码的代码也可以省略。 如果这样的省略使得条件分支上的两条path的代码相同,则该条件可以被视为未被使用和省略。 这对任何不会超出内存访问范围的程序的行为(除执行时间之外)都没有影响,或者调用附录L所称的“关键的未定义行为”。 -
如果编译器确定计算值的机器码只能产生一定范围内的结果,则可能会忽略任何可以在此基础上预测结果的条件testing。 如上所述,除非代码调用“Critical Undefined Behaviors”,否则这不会影响执行时间以外的行为。
-
如果编译器确定某些input会调用任何forms的未定义行为,而且编写的代码会使编译器忽略任何只在接收到这种input时才相关的代码,即使执行平台的自然行为因为这样的input本来是良性的,编译器的重写会使其变得危险。
好的编译器做#1和#2。 不过,出于某种原因,#3已经成为时尚。