¿Cómo optimiza GCC una variable no utilizada incrementada dentro de un bucle?

 C Programming >> Programación C >  >> Tags >> GCC
¿Cómo optimiza GCC una variable no utilizada incrementada dentro de un bucle?


Escribí este sencillo programa en C:


int main() {
int i;
int count = 0;
for(i = 0; i < 2000000000; i++){
count = count + 1;
}
}

Quería ver cómo el compilador gcc optimiza este ciclo (claramente agregue 1 2000000000 veces debería ser "añadir 2000000000 una vez"). Entonces:


prueba gcc.c y luego time el a.out da:


real 0m7.717s  
user 0m7.710s
sys 0m0.000s

$ gcc -O2 prueba.c y luego time on a.out` da:


real 0m0.003s  
user 0m0.000s
sys 0m0.000s

Luego desarmé ambos con gcc -S . El primero parece bastante claro:


    .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 añade, L2 compara -4(%rbp) con 1999999999 y vuelve a L3 si i < 2000000000 .


Ahora el optimizado:


    .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

No puedo entender en absoluto lo que está pasando allí! Tengo poco conocimiento de ensamblaje, pero esperaba algo como


addl $2000000000, -8(%rbp)

Incluso probé con gcc -c -g -Wa,-a,-ad -O2 test.c para ver el código C junto con el ensamblaje al que se convirtió, pero el resultado no fue más claro que el anterior.


Alguien puede explicar brevemente:



  1. El gcc -S -O2 salida.

  2. ¿Si el bucle está optimizado como esperaba (una suma en lugar de muchas sumas)?


Respuestas:


El compilador es aún más inteligente que eso. :)


De hecho, se da cuenta de que no estás usando el resultado del ciclo. ¡Así que eliminó todo el bucle por completo!


Esto se llama Eliminación de código inactivo.


Una mejor prueba es imprimir el resultado:


#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);
}

EDITAR: He agregado el #include <stdio.h> requerido; la lista de ensamblaje de MSVC corresponde a una versión sin el #include , pero debería ser lo mismo.



No tengo GCC frente a mí en este momento, ya que estoy arrancado en Windows. Pero aquí está el desmontaje de la versión con el printf() en MSVC:


EDITAR:tuve una salida de ensamblaje incorrecta. Este es el correcto.


; 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:[email protected]@[email protected]
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

Entonces sí, Visual Studio hace esta optimización. Supongo que GCC probablemente también lo haga.


Y sí, GCC realiza una optimización similar. Aquí hay una lista de ensamblaje para el mismo programa con 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