Compiler sind nicht perfekt. Gerade das Optimieren von Code ist so komplex, dass es keinen Algorithmus dafür gibt. Ehr werden Heuristiken dafür benutzt. So kommt es schon mal vor, dass bei banal aussehenden Code die Compiler Optimierung versagt hat und es möglich ist, von Hand schnelleren Assembler Code zu schreiben.
In [1] wurde viele solcher fehlerhaften Optimierungen gezeigt. Mit "Fehlerbeschreibung", Assembercode und teils sogar Patches für den Compiler. Leider nur für die ARM Architektur.
Ich möchte nun einige Beispiele aufgreifen und versuchen sie auf X86 64bit Plattformen nachzuahmen. Getestet wird mit g++ 64bit 4.9.2, 7.1.0, 32bit 6.3.0. Compileraufruf:
g++ -save-temps -O3 -g -fverbose-asm -march=native -c 1.cpp -Wa,-adhln=test.s
Compiler | passed | failed |
gcc 8.0.0 ARM | 0 | 2 |
g++ 7.1.0 x86-64 | 2 | 0 |
g++ 6.0.0 x86-32 | 1 | 1 |
g++ 4.9.2 x86-64 | 2 | 0 |
Useless initialization of struct passed by value
struct S0 { int f0; int f1; int f2; int f3; }; int f1(struct S0 p) { return p.f0; }
ARM:
The struct is passed in registers, and the function's result is already in r0, which is also the return register. The function could return immediately, but GCC first stores all the struct fields to the stack and reloads the first field.
X86:
11:1.cpp **** return p.f0; 71 movl %edi, %eax # p, 72 ret
Ja, nur eine Assembler Instruktion.
float to char type conversion goes through memory
char fn1(float p1) { return (char) p1; }
ARM:
the result of the conversion in s15 is stored to the stack and then reloaded into r0 instead of just copying it between the registers
X86-64:
2:2.cpp **** return (char) p1; 78 vcvttss2si %xmm0, %eax # p1
VCVTTSS2SI: Convert one single-precision floating-point value from xmm1/m32 to one signed doubleword integer in r32 using truncation.
Ja, mit AVX zwischen den Register kopiert.
X86-32:
78 subl $4, %esp #, 2:2.cpp **** return (char) p1; 81 flds 8(%esp) # p1 82 fisttps 2(%esp) # 83 movzwl 2(%esp), %eax #
FISTTP: Store Integer with Truncation
Nein, die Variable wird vom Stack in ein floating point Register geladen und dort convertiert. Danach zurück auf den Stack gespeichert und zum Schluss in das Rückgaberegister geladen.
[1] https://github.com/gergo-/missed-optimizations/blob/master/README.md