Kann ein Compiler kürzen und optimiert er dann?
-
Folgender Code:
[cpp]
int x;
cin < x;
x = (x * 2)/4
[cpp]Frage, erkennt er, daß er in der 3. Zeile kürzen kann und dann nur noch einen rechtsshift braucht, anstatt einen langsamen div Befehl?
Wie gut optimieren hier die Compiler?
Wann und wo sind deren Grenzen der automatisierten Optimierung erreicht und ab wann ist es besser, wenn der Mensch selber Hand anlegt und Assemblercode für den kurzen Codeabschnitt schreibt?
-
Mist, falsches Forum. Eigentlich wollte ich das in "Rund um die Programmierung" posten.
Könnte das ein Mod verschieben?
-
Dieser Thread wurde von Moderator/in nman aus dem Forum Themen rund um den PC in das Forum Rund um die Programmierung verschoben.
Im Zweifelsfall bitte auch folgende Hinweise beachten:
C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?Dieses Posting wurde automatisch erzeugt.
-
Multiplikationen/Divisionen in Shifts umwandeln ist trivial und macht eigentlich jeder Compiler. In Deinem Fall braucht er aber nicht nur ein Shift, sondern 2 Shifts (oder 1 Shift+Maske) - die Multiplikation könnte überlaufen.
-
Er muss einmal links und einmal rechts schieben, das zusammenzufassen sollte nicht das große Problem sein. Beim Überlauf ist das Verhalten undefiniert, darum muss er sich also nicht kümmern.
~edit: deutsche sprache schwere sprache~
-
this.optimize schrieb:
Wie gut optimieren hier die Compiler?
Probiers doch aus
-
SG1 schrieb:
In Deinem Fall braucht er aber nicht nur ein Shift, sondern 2 Shifts (oder 1 Shift+Maske) - die Multiplikation könnte überlaufen.
Muss er das wirklich? Überlauf bei signed int ist in C++ undefiniert, insofern müsste der Compiler die Maske auch ignorieren dürfen, oder?
edit: Bashar war schneller.
-
Jeder vernünftige Compiler definiert das Verhalten bei Überlauf.
Ob das vom Standard vorgeschrieben (implementation-defined vs. undefined) ist müsste ich nachsehen, aber darauf kommts nicht an. Zu behaupten ein Compiler könnte daraus evtl. nur einen einzigen Shift machen ist daher entweder falsch oder zumindest weltfremd.
-
Visual Studio 2008 Standardeinstellungen
int main() { 00FE13B0 push ebp 00FE13B1 mov ebp,esp 00FE13B3 sub esp,0CCh 00FE13B9 push ebx 00FE13BA push esi 00FE13BB push edi 00FE13BC lea edi,[ebp-0CCh] 00FE13C2 mov ecx,33h 00FE13C7 mov eax,0CCCCCCCCh 00FE13CC rep stos dword ptr es:[edi] int x; cin >> x; 00FE13CE mov esi,esp 00FE13D0 lea eax,[x] 00FE13D3 push eax 00FE13D4 mov ecx,dword ptr [__imp_std::cin (0FE8298h)] 00FE13DA call dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (0FE8290h)] 00FE13E0 cmp esi,esp 00FE13E2 call @ILT+320(__RTC_CheckEsp) (0FE1145h) x = (x * 2)/4; 00FE13E7 mov eax,dword ptr [x] 00FE13EA shl eax,1 00FE13EC cdq 00FE13ED and edx,3 00FE13F0 add eax,edx 00FE13F2 sar eax,2 00FE13F5 mov dword ptr [x],eax cout << x; 00FE13F8 mov esi,esp 00FE13FA mov eax,dword ptr [x] 00FE13FD push eax 00FE13FE mov ecx,dword ptr [__imp_std::cout (0FE8294h)] 00FE1404 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0FE829Ch)] 00FE140A cmp esi,esp 00FE140C call @ILT+320(__RTC_CheckEsp) (0FE1145h) return 1; 00FE1411 mov eax,1 }
-
MisterX schrieb:
Visual Studio 2008 Standardeinstellungen
00FE13C2 mov ecx,33h 00FE13C7 mov eax,0CCCCCCCCh 00FE13CC rep stos dword ptr es:[edi] [...] 00FE140C call @ILT+320(__RTC_CheckEsp) (0FE1145h)
Ich will ja nix sagen, aber bist du dir sicher, dass das keine Debugversion ist?
-
Bashar schrieb:
MisterX schrieb:
Visual Studio 2008 Standardeinstellungen
00FE13C2 mov ecx,33h 00FE13C7 mov eax,0CCCCCCCCh 00FE13CC rep stos dword ptr es:[edi] [...] 00FE140C call @ILT+320(__RTC_CheckEsp) (0FE1145h)
Ich will ja nix sagen, aber bist du dir sicher, dass das keine Debugversion ist?
Ups... hab tatsächlich auf debug übersetzt.
Bei Release Version kommt dieses:int main() { 012D1000 push ecx int x; cin >> x; 012D1001 mov ecx,dword ptr [__imp_std::cin (12D203Ch)] 012D1007 lea eax,[esp] 012D100A push eax 012D100B call dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (12D2038h)] x = (x * 2)/4; 012D1011 mov eax,dword ptr [esp] cout << x; 012D1014 mov ecx,dword ptr [__imp_std::cout (12D2044h)] 012D101A add eax,eax 012D101C cdq 012D101D and edx,3 012D1020 add eax,edx 012D1022 sar eax,2 012D1025 push eax 012D1026 mov dword ptr [esp+4],eax 012D102A call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (12D2040h)] return 1; 012D1030 mov eax,1 }
-
hustbaer schrieb:
Jeder vernünftige Compiler definiert das Verhalten bei Überlauf.
Ob das vom Standard vorgeschrieben (implementation-defined vs. undefined) ist müsste ich nachsehen, aber darauf kommts nicht an. Zu behaupten ein Compiler könnte daraus evtl. nur einen einzigen Shift machen ist daher entweder falsch oder zumindest weltfremd.gcc ist unvernünftig und weltfremd?
Natürlich kann kein einfaches shift herauskommen, sondern nur ein solches, dass das Vorzeichen bewahrt.Untersucht doch mal, was bei dem hier rauskommt:
int x, y; cin >> x; x = x * x; y = ( x * 2 ) / 4;
x kann nicht negativ werden (ohne Überlauf). Hier wird ein schlauer Compiler also ein einfaches shift draus machen.
-
camper schrieb:
hustbaer schrieb:
Jeder vernünftige Compiler definiert das Verhalten bei Überlauf.
Ob das vom Standard vorgeschrieben (implementation-defined vs. undefined) ist müsste ich nachsehen, aber darauf kommts nicht an. Zu behaupten ein Compiler könnte daraus evtl. nur einen einzigen Shift machen ist daher entweder falsch oder zumindest weltfremd.gcc ist unvernünftig und weltfremd?
Natürlich kann kein einfaches shift herauskommen, sondern nur ein solches, dass das Vorzeichen bewahrt.Untersucht doch mal, was bei dem hier rauskommt:
int x, y; cin >> x; x = x * x; y = ( x * 2 ) / 4;
x kann nicht negativ werden (ohne Überlauf). Hier wird ein schlauer Compiler also ein einfaches shift draus machen.
Als Release kommt:
int main() { 00091000 push ecx int x, y; cin >> x; 00091001 mov ecx,dword ptr [__imp_std::cin (9203Ch)] 00091007 lea eax,[esp] 0009100A push eax 0009100B call dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (92038h)] x = x * x; 00091011 mov eax,dword ptr [esp] y = ( x * 2 ) / 4; cout << x; 00091014 mov ecx,dword ptr [__imp_std::cout (92044h)] 0009101A imul eax,eax 0009101D push eax 0009101E mov dword ptr [esp+4],eax 00091022 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (92040h)] return 1; 00091028 mov eax,1 }
-
Ahhh Schei.. es sollte bestimmt y ausgegeben werden ^^
int main() { 00E21000 push ecx int x, y; cin >> x; 00E21001 mov ecx,dword ptr [__imp_std::cin (0E2203Ch)] 00E21007 lea eax,[esp] 00E2100A push eax 00E2100B call dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (0E22038h)] x = x * x; 00E21011 mov eax,dword ptr [esp] y = ( x * 2 ) / 4; cout << y; 00E21014 mov ecx,dword ptr [__imp_std::cout (0E22044h)] 00E2101A imul eax,eax 00E2101D mov dword ptr [esp],eax 00E21020 add eax,eax 00E21022 cdq 00E21023 and edx,3 00E21026 add eax,edx 00E21028 sar eax,2 00E2102B push eax 00E2102C call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0E22040h)] return 1; 00E21032 mov eax,1 }
-
Das wäre dann ja noch was, das am gcc verbessert werden könnte...
-
hustbaer schrieb:
Jeder vernünftige Compiler definiert das Verhalten bei Überlauf.
Ob das vom Standard vorgeschrieben (implementation-defined vs. undefined) ist müsste ich nachsehen, aber darauf kommts nicht an. Zu behaupten ein Compiler könnte daraus evtl. nur einen einzigen Shift machen ist daher entweder falsch oder zumindest weltfremd.LLVM und clang, an denen gerade gut entwickelt wird, scheinen bei undefiniertem Verhalten deutlich agressiver vorzugehen als bisherige Compiler:
http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.htmlFor example, knowing that INT_MAX+1 is undefined allows optimizing "X+1 > X" to "true". Knowing the multiplication "cannot" overflow (because doing so would be undefined) allows optimizing "X*2/2" to "X".
edit: Es gibt auch eine Fortsetzung des ersten Blogposts, wo noch ein paar Punkte erwähnt werden: http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html
-
Der Artikel (und der von dort verlinkte Artikel) ist unbedingt lesenswert.
-
Bashar schrieb:
Beim Überlauf ist der Verhalten undefiniert, darum muss er sich also nicht kümmern.
Gnar, ich vergaß. Asche auf mein Haupt.
-
Hm.
Wie man sich irren kann.Chris Lattner schrieb:
Why undefined behavior is often a scary and terrible thing for C programmers
Dem kann ich mich nur anschliessen.
ps
regehr schrieb:
Basically: be very careful, use good tools, and hope for the best.
hope for the best
-
Bashar schrieb:
Er muss einmal links und einmal rechts schieben, das zusammenzufassen sollte nicht das große Problem sein. Beim Überlauf ist das Verhalten undefiniert, darum muss er sich also nicht kümmern.
~edit: deutsche sprache schwere sprache~
Aber wozu soll er nach links schieben?
Wenn er kürzt, dann fliegt die Multiplikaion raus, übrig bleibt eine Division durch 2 die mit einem einzigen rechtsshift lösbar ist.