Is it just me, or …

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Is it just me, or …

Beitrag von Krishty »

… ist mein Compiler blind?

Hi,

Ich arbeite mit VS 2010 und habe, um einen Überblick über die Optimierungsmöglichkeiten zu kriegen, mal meine Kompilate mit den Quelltexten verglichen …
Zuerst muss ich sagen: Der Optimizer ist gut. Er leistet stellenweise so gute Arbeit, dass ich mit meinen Assembler-Basiskenntnissen nicht mehr nachvollziehen kann, was in bestimmten Codeabschnitten vorgeht (was auch der Grund ist, warum ich mich jetzt hier erkundigen möchte).
Dann gibt es aber auch Stellen, an denen – meiner Meinung nach – offensichtliche Optimierungsmöglichkeiten schlicht ignoriert werden. Das sind vor allem: Aussagenlogik mit integralen Typen, Exception-Overhead und vftables in abstrakten Klassen.


Aussagenlogik mit integralen Typen:
Mir stieß schon in dem Thread über den möglichst schnellen Point-in-Rect-Test bitter auf, dass der Compiler alle Ausdrücke behandelt, als ob bei ihrer Auswertung Side-Effects aufträten – auch, wenn es sich nur um Ints handelt. Ein Beispiel: Testen, ob einer von drei Zeigern nicht nullptr ist.

Code: Alles auswählen

// 1)
if((nullptr != p1) || (nullptr != p2) || (nullptr != p3))
	// Kompiliert zu sechs Befehlen, davon drei bedingte Sprünge
	
// 2)
if((nullptr != p1) | (nullptr != p2) | (nullptr != p3))
	// Kompiliert zu zwölf Befehlen, davon ein bedingter Sprung

// 3)
if(uintptr_t(p1) | uintptr_t(p2) | uintptr_t(p3))
	// Kompiliert zu vier Befehlen, davon ein bedingter Sprung
Von einem Compiler, in dem >15 Jahre C-/C++-Erfahrung steckt, würde ich auf höchster Optimierungsstufe zumindest die Leistung von 2) und 3) erwarten, auch, wenn mein Quelltext wie 1) aussieht. Ist das wirklich so kompliziert oder haben die Gründe, das nicht zu tun?


Exception-Overhead:
Der Compiler behandelt jedes throw mit inline. Wenn man mal darüber nachdenkt, ist das doch wahnwitzig: throw impliziert doch, dass der Codeabschnitt bestenfalls nie benutzt werden wird und dass die Performance des umliegenden Codes wichtiger ist, sonst würde man return verwenden. Außerdem ist das Schmeißen einer Exception von sich aus teuer wie nichts, aber trotzdem optimiert es der Compiler, als ginge es um Leben und Tod. Beispiel:

Code: Alles auswählen

// Üblich:
class CException : public ::std::exception {
public:
	CException() throw() { }
	virtual char const * what() const throw() { return "dummy"; }
};

if(!SomeCrucialCondition)
	throw CException; // Kompiliert zu sechs Befehlen, bei komplexeren Exception-Klassen auch gern zu elf oder >20.


// Dem gegenüber:
class CException : public ::std::exception {
protected:
	CException() throw() { }
public:
	virtual char const * what() const throw() { return "dummy"; }
	__declspec(noinline) __declspec(noreturn) static void Throw() { throw CException(); }
};

if(!SomeCrucialCondition)
	CException::Throw(); // Kompiliert zu *einem* Befehl
Die zweite Version ist, wenn eine Exception auftritt, einen Aufruf länger (ich spreche hier extra nicht von „schneller“ oder „langsamer“, weil ich es nicht gebencht habe) als die erste. Wenn allerdings keine Exception auftritt, was ja die Regel sein sollte, spart man fünf Sechstel des Exception-Overheads. Allein das Auswechseln des throws hat mein Kompilat fast zehn Prozent kleiner gemacht! (Noch krasser wird der Unterschied, wenn man Fehlermeldungen mit auslagert.)
Wenn der Compiler schon sonst nicht weiß, welcher Pfad bevorzugt behandelt werden muss: throw ist doch der Wink mit dem Zaunpfahl schlechthin, dass dem Pfad Geschwindigkeit egal ist. Der Compiler müsste mit Leichtigkeit erkennen können, dass derselbe throw-Code hunderte Male an unkritischen Stellen im Code vorkommt, es mit Leichtigkeit auslagern können und COMDAT-Folding würde dann den Rest erledigen. Auch hier wieder: Ist das Absicht? (Unter x64 sieht die Situation glücklicherweise besser aus, wenn auch bloß architekturbedingt.)


vftables in abstrakten Klassen:
__declspec(novtable) erlaubt dem Compiler, den vftable einer abstrakten Klasse wegzulassen – da abstrakte Klassen ja sowieso nicht instanziiert werden können. Nette kleine Optimierung, die wieder einen unwesentlichen bis beachtlichen Overhead aus dem Kompilat treibt – je nachdem, wie viele oder wie wenige abstrakte Klassen man benutzt. Warum macht der Compiler das nicht automatisch? Gibt es überhaupt einen Fall, in dem der vftable einer abstrakten Klasse zur Laufzeit erreichbar sein muss?


Ich hoffe, ihr könnt mich erleuchten …

Gruß, Ky
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Tobiking
Beiträge: 16
Registriert: 27.02.2010, 23:55

Re: Is it just me, or …

Beitrag von Tobiking »

Krishty hat geschrieben:Aussagenlogik mit integralen Typen:
Mir stieß schon in dem Thread über den möglichst schnellen Point-in-Rect-Test bitter auf, dass der Compiler alle Ausdrücke behandelt, als ob bei ihrer Auswertung Side-Effects aufträten – auch, wenn es sich nur um Ints handelt. Ein Beispiel: Testen, ob einer von drei Zeigern nicht nullptr ist.

Code: Alles auswählen

// 1)
if((nullptr != p1) || (nullptr != p2) || (nullptr != p3))
	// Kompiliert zu sechs Befehlen, davon drei bedingte Sprünge
	
// 2)
if((nullptr != p1) | (nullptr != p2) | (nullptr != p3))
	// Kompiliert zu zwölf Befehlen, davon ein bedingter Sprung

// 3)
if(uintptr_t(p1) | uintptr_t(p2) | uintptr_t(p3))
	// Kompiliert zu vier Befehlen, davon ein bedingter Sprung
Von einem Compiler, in dem >15 Jahre C-/C++-Erfahrung steckt, würde ich auf höchster Optimierungsstufe zumindest die Leistung von 2) und 3) erwarten, auch, wenn mein Quelltext wie 1) aussieht. Ist das wirklich so kompliziert oder haben die Gründe, das nicht zu tun?
Ich denke der Compiler kann an der Stelle nicht erkennen ob Variante 1) oder 2) besser ist und hält sich daher strikt an das was der Programmierer ihm sagt. Variante 1) hat zwar sechs Befehle, aber wenn die erste Aussage wahr ist, werden nur zwei Befehle ausgeführt. Ist die erste Aussage also oft erfüllt, ist diese Lösung besser. Sind allerdings die ersten beiden Aussagen oft nicht erfüllt, kommt es auf die branch prediction des Prozessors an wie schlecht diese Lösung ist.

Den Unterschied bei 2) und 3) seh ich gerade nicht. Ich habe auch grad kein VS da zum testen, sondern nur einen gcc. Der erstellt bei beiden zwar unterschiedlichen Code, allerdings nach dem gleichen Prinzip und kein sichtbarer Unterschied im Aufwand.
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Tobiking hat geschrieben:Ich denke der Compiler kann an der Stelle nicht erkennen ob Variante 1) oder 2) besser ist und hält sich daher strikt an das was der Programmierer ihm sagt. Variante 1) hat zwar sechs Befehle, aber wenn die erste Aussage wahr ist, werden nur zwei Befehle ausgeführt. Ist die erste Aussage also oft erfüllt, ist diese Lösung besser. Sind allerdings die ersten beiden Aussagen oft nicht erfüllt, kommt es auf die branch prediction des Prozessors an wie schlecht diese Lösung ist.
Der Knackpunkt ist gerade, dass es – selbst, wenn Aussage 1 oft wahr ist – auf die Branch-Prediction des Prozessors ankommt und nicht generell besser ist, denn es ist ja nicht gesagt, dass sich der Prozessor immer automatisch für den Weg aus dem Vergleich heraus entscheidet. Nur kommt es bei 1) eben dreimal so stark auf die Branch-Prediction an wie bei 2) und 3). Das „ich weiß nicht, was am besten ist, also mache ich einfach garnichts“-Argument vermute ich bei VC++ auch, finde es aber komisch, dass die Compiler bei so fundamentalen Situationen ratlos sind.
Tobiking hat geschrieben:Den Unterschied bei 2) und 3) seh ich gerade nicht. Ich habe auch grad kein VS da zum testen, sondern nur einen gcc. Der erstellt bei beiden zwar unterschiedlichen Code, allerdings nach dem gleichen Prinzip und kein sichtbarer Unterschied im Aufwand.
Der „abgewickelte“ Code sähe jeweils so aus:

2) Erst vergleichen, dann verknüpfen

Code: Alles auswählen

bool const p1IsNotNullptr = (nullptr != p1);
bool const p2IsNotNullptr = (nullptr != p2);
bool const p3IsNotNullptr = (nullptr != p3);
bool const Result = (p1IsNotNullptr | p2IsNotNullptr | p3IsNotNullptr);
3) Erst verknüpfen, dann vergleichen

Code: Alles auswählen

uintptr_t const AllPointersOred = (uintptr_t(p1) | uintptr_t(p2) | uintptr_t(p3));
bool const Result = (0 != AllPointersOred);
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Tobiking
Beiträge: 16
Registriert: 27.02.2010, 23:55

Re: Is it just me, or …

Beitrag von Tobiking »

Das Verhalten zu 2) und 3) ist auf jeden Fall komisch. Wenn dem Compiler bekannt ist das nullptr 0 ist sollte der gleiche Code herauskommen.

Ich habe meinen Test mit gcc mal ausgebaut, so dass ich nun auch vollständige Optimierung aktivieren konnte. Das Ergebnis ist bei allen Fällen der gleiche Code. Die Vorgehensweise entspricht immer dem Fall 1. Das würde heißen der gcc erkennt schon mal das die Integer keine Seiteneffekte haben können. Interessant ist dabei vielleicht noch die Reihenfolge:

Code: Alles auswählen

Test Aussage 1
Falls nicht erfüllt Springe zu Test 2
Rumpf
Springe ans Ende
Test 2
Falls erfüllt Sprung zu Rumpf
Test 3
Falls erfüllt Sprung zu Rumpf
Laut Wikipedia werden bei der branch prediction Sprünge nach hinten meistens gewählt, weil diese auf Schleifen hindeuten. Das sieht allgemein so aus als wenn der gcc sehr optimistisch ist, und davon ausgeht das die Bedingung erfüllt wird. Ob das optimal ist, bleibt aber eine andere Frage.
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Tobiking hat geschrieben:Ich habe meinen Test mit gcc mal ausgebaut, so dass ich nun auch vollständige Optimierung aktivieren konnte. Das Ergebnis ist bei allen Fällen der gleiche Code. Die Vorgehensweise entspricht immer dem Fall 1. […] Ob das optimal ist, bleibt aber eine andere Frage.
Bemerkenswert – VCpp scheint da also tatsächlich einen Knacks zu haben …

Ob der GCC-Code in diesem Beispiel optimal ist, ist eigentlich zweitrangig – wenn der Compiler nicht in jedem Fall optimalen Code produziert, ich aber weiß, dass er gewisse Kenntnisse über Logik und Wirkungen hat, dann ist mir das mehr geheuer als wenn er Optimierungen einfach generell sein lässt.

Auf jeden Fall danke für den Test, ich habe hier keinen GCC zur Verfügung.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: Is it just me, or …

Beitrag von eXile »

Ich wäre von meiner Seite aus noch sehr an einem Vergleich der Kompilierungsoptionen /O1 (Größe minimieren) und /O2 (Geschwindigkeit maximieren) interessiert!
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Keine Wirkung.
(ist ein real-life-Beispiel, synthetische Benchmarks auf höchster Optimierungsstufe sind zu anstrengend)

Code: Alles auswählen

full optimization (/Ox)
		if((nullptr != PNGPointer) || (nullptr !=PNGPreImageInfoPointer) || (nullptr !=PNGPostImageInfoPointer))
008E1051  cmp         dword ptr [PNGPointer],0  
008E1055  jne         __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+15h (8E1063h)  
008E1057  cmp         dword ptr [PNGPreImageInfoPointer],0  
008E105B  jne         __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+15h (8E1063h)  
008E105D  cmp         dword ptr [PNGPostImageInfoPointer],0  
008E1061  je          __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+29h (8E1077h)  

minimize size (/O1)
		if(PNGPointer || PNGPreImageInfoPointer || PNGPostImageInfoPointer)
00A9F143  xor         esi,esi  
00A9F145  cmp         dword ptr [PNGPointer],esi  
00A9F148  jne         __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+14h (0A9F154h)  
00A9F14A  cmp         dword ptr [PNGPreImageInfoPointer],esi  
00A9F14D  jne         __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+14h (0A9F154h)  
00A9F14F  cmp         dword ptr [PNGPostImageInfoPointer],esi  
00A9F152  je          __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+28h (0A9F168h)  

maximize speed (/O2)
		if(PNGPointer || PNGPreImageInfoPointer || PNGPostImageInfoPointer)
00FD1051  cmp         dword ptr [PNGPointer],0  
00FD1055  jne         __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+15h (0FD1063h)  
00FD1057  cmp         dword ptr [PNGPreImageInfoPointer],0  
00FD105B  jne         __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+15h (0FD1063h)  
00FD105D  cmp         dword ptr [PNGPostImageInfoPointer],0  
00FD1061  je          __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+29h (0FD1077h)  



full optimization (/Ox)
		if((nullptr != PNGPointer) | (nullptr !=PNGPreImageInfoPointer) | (nullptr !=PNGPostImageInfoPointer))
00961051  xor         eax,eax  
00961053  cmp         dword ptr [PNGPostImageInfoPointer],eax  
00961056  setne       al  
00961059  xor         ecx,ecx  
0096105B  cmp         dword ptr [PNGPreImageInfoPointer],ecx  
0096105E  setne       cl  
00961061  or          eax,ecx  
00961063  xor         edx,edx  
00961065  cmp         dword ptr [PNGPointer],edx  
00961068  setne       dl  
0096106B  or          eax,edx  
0096106D  je          __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+35h (961083h)  

minimize size (/O1)
		if((nullptr != PNGPointer) | (nullptr !=PNGPreImageInfoPointer) | (nullptr !=PNGPostImageInfoPointer))
00B8F143  xor         eax,eax  
00B8F145  xor         esi,esi  
00B8F147  cmp         dword ptr [PNGPostImageInfoPointer],esi  
00B8F14A  setne       al  
00B8F14D  xor         ecx,ecx  
00B8F14F  cmp         dword ptr [PNGPreImageInfoPointer],esi  
00B8F152  setne       cl  
00B8F155  or          eax,ecx  
00B8F157  xor         ecx,ecx  
00B8F159  cmp         dword ptr [PNGPointer],esi  
00B8F15C  setne       cl  
00B8F15F  or          eax,ecx  
00B8F161  je          __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+37h (0B8F177h)  

maximize speed (/O2)
		if((nullptr != PNGPointer) | (nullptr !=PNGPreImageInfoPointer) | (nullptr !=PNGPostImageInfoPointer))
00ED1051  xor         eax,eax  
00ED1053  cmp         dword ptr [PNGPostImageInfoPointer],eax  
00ED1056  setne       al  
00ED1059  xor         ecx,ecx  
00ED105B  cmp         dword ptr [PNGPreImageInfoPointer],ecx  
00ED105E  setne       cl  
00ED1061  or          eax,ecx  
00ED1063  xor         edx,edx  
00ED1065  cmp         dword ptr [PNGPointer],edx  
00ED1068  setne       dl  
00ED106B  or          eax,edx  
00ED106D  je          __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+35h (0ED1083h)  



full optimization (/Ox)
		if(CByteSize(PNGPointer) | CByteSize(PNGPreImageInfoPointer) | CByteSize(PNGPostImageInfoPointer))
00201051  mov         eax,dword ptr [PNGPostImageInfoPointer]  
00201054  or          eax,dword ptr [PNGPreImageInfoPointer]  
00201057  or          eax,dword ptr [PNGPointer]  
0020105A  je          __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+22h (201070h)  

minimize size (/O1)
		if(CByteSize(PNGPointer) | CByteSize(PNGPreImageInfoPointer) | CByteSize(PNGPostImageInfoPointer))
0116F143  mov         eax,dword ptr [PNGPostImageInfoPointer]  
0116F146  or          eax,dword ptr [PNGPreImageInfoPointer]  
0116F149  or          eax,dword ptr [PNGPointer]  
0116F14C  je          __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+22h (116F162h)  

maximize speed (/O2)
		if(CByteSize(PNGPointer) | CByteSize(PNGPreImageInfoPointer) | CByteSize(PNGPostImageInfoPointer))
011D1051  mov         eax,dword ptr [PNGPostImageInfoPointer]  
011D1054  or          eax,dword ptr [PNGPreImageInfoPointer]  
011D1057  or          eax,dword ptr [PNGPointer]  
011D105A  je          __catch$?ReadImageWith@PNG@FileFormats@Cric@@YA?AVCImage@Graphics@3@AAVCReader@Memory@3@W4Value@EFormat@53@M@Z$0+22h (11D1070h)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Jörg »

Wie vertragen sich denn novtable und default-Implementierungen von pure-virtual functions?

Also z.b.:

a.h
class A {
~A() = 0;
};

a.cpp
A:~A() {}

Wenn das eintragen der vtable wirklich mal ein Performance-Problem wird, dann wuesste ich andere Wege, das zu umgehen....

PS: Da habe ich eine Frage formuliert ohne auf das urspruengliche Problem hinzuweisen....ein automatisches Erkennen von "novtable" funktioniert nur bei einem globalen Optimierungsvorgang....sonst weiss der Compiler nicht, ob nicht irgendwo so eine heimtueckische "ich bin die pure-virtual Implementierung" herumliegt (und benutzt wird). Und ich mag mir nicht vorstellen, was passiert, wenn die ganze vtable fuer diese Basisklasse im Orkus gelandet ist. Lange Reder, kurzer Sinn: Eine Automatik waere wohl schwierig und gefaehrlich, mit der declspec-Variante weiss man ja, wen man verantwortlich machen muss.
Der Sinn dieses Konstrukts liegt wohl woanders: Ich kann z.B. inplace-Konstruktoren aufrufen, welche eine vorher eingetragene vtable nicht (mehr) ueberschreiben.
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Ich wusste garnicht, dass man abstrakte Funktionen später noch definieren kann (und kann die Frage ergo auch nicht beantworten) … scheinbar, um auch bei implementierten Funktionen abgeleitete Klassen zu zwingen, die Funktionen zu überladen? Wieder was gelernt.

Mit deiner (nachgetragenen) Antwort kann ich was anfangen, danke :) Aber afaik hat Visual C++ einen solchen globalen Optimierungsvorgang doch mit Link-Time Code Generation implementiert, oder? Also stammt das novtable-Keyword noch aus alten Zeiten und die Automatik ist einfach nicht implementiert, weil sie bei zuviel Aufwand zuwenig Gewinn abwerfen würde.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Jörg
Establishment
Beiträge: 296
Registriert: 03.12.2005, 13:06
Wohnort: Trondheim
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Jörg »

'novtable' ist wirklich alt (eingefuehrt mit Visual Studio 5, um etwas Overhead bei ATL&COM zu sparen), LTCG gab es damals noch nicht bei MS.
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Ich möchte hier nochmal anmerken, dass Visual C++ es versäumt Funktionen zu inlinen, die nur einmal referenziert werden. Man kann ordentlich nachoptimieren, wenn man diese Funktionen von Hand mit __forceinline dekoriert – bei mir sind gerade wieder über 3 KiB .text rausgeflogen, nachdem ich fünf Funktionen ge-force-inline-d habe … normalerweise fällt die Ersparnis geringer aus, aber manchmal trifft man eben einen Punkt, an dem der Optimizer den Code dadurch komplett umkrempeln kann.

Zum Verständnis:

Code: Alles auswählen

void Foo(
	char const * A, char const * B,
	char const * C, char const * D,
	char const * E
) {
	// Möglichst viele Funktionsaufrufe, damit die Funktion für konventionelles Inlining zu lang wird.
	::std::cout << A << ' ' << B << ' ' << C << ' ' << D << ' ' << E << ::std::endl;
}

int main() {
	// Verhindern, dass die Parameter wegoptimiert werden.
	static char const * Text[] = { "Bloss", "ein", "kleiner", "Text", "am Rande" };
	Foo(Text[0], Text[1], Text[2], Text[3], Text[4]);
	return 0;
}
Das kompiliert zu 69 Befehlen (59 in der Funktion, zehn beim Aufruf). Dekoriert man Foo() mit __forceinline, sind es nurnoch 47 beim Aufruf – also über 30 % Ersparnis. GCC implementiert das übrigens schon; für VC habe ich mal eine Feature-Request abgeschickt.

Ich könnte das hier langsam zu einem VC-Optimierung-Nachhilfethread ausbauen :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Leute, lasst leere Destruktoren (~MyClass(){}) weg. VC scheint erhebliche Probleme damit zu haben, die wegzuoptimieren … u.a. landen leere Destruktoren in der atexit-Liste, falls Objekte des Typs static deklariert sind. Der Hammer ist aber, dass der Compiler erst anfängt, SSE zu benutzen, wenn bei beteiligten Typen keine D’toren definiert sind …

… also, wenn ihr im D’tor nichts zu sagen habt, löscht das Ding sofort, denn VC wird es wahrscheinlich nicht wegoptimieren.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
jgl
Establishment
Beiträge: 109
Registriert: 08.04.2009, 08:58

Re: Is it just me, or …

Beitrag von jgl »

Vielen Dank für die Info. :)
Wie kamst Du auf die Erkenntnis?
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Mein Code wurde kleiner, nachdem ich ein paar leere D’toren gelöscht habe, da war ich schon skeptisch. Als ich dann die CRT nachprogrammiert habe, fiel mir auf, dass leere D’toren in der atexit-Liste auftauchten (eine Liste aller initialisierter statischer Instanzen des Programms, die die Runtime führt, damit deren Destruktoren beim Programmende aufgerufen werden) – da war ich dann alarmiert und habe die entsprechenden D’toren gelöscht. An dem Punkt wusste ich, dass der Compiler zumindest Probleme mit leeren D’toren hat …

… aber nachdem ich alle leeren D’toren gelöscht hatte ist mein Code größenmäßig geradezu explodiert. Dank Jörg kam ich auf die Spur von einigen meiner Funktionen, die plötzlich komplett mit SSE arbeiteten statt mit der FPU – da wurde es mir dann zuviel und ich habe einen Bug-Report gepostet … mal sehen, ob sich was tut, schließlich ist man auch manchmal gezwungen, leere D’toren anzugeben, bspw. wenn sie nicht public sein sollen oder eine spezielle Exception-specification brauchen. (Das Beispiel im Report kompiliert zu 45 Befehlen ohne D’tor, zu 94 mit leerem D’tor.)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
jgl
Establishment
Beiträge: 109
Registriert: 08.04.2009, 08:58

Re: Is it just me, or …

Beitrag von jgl »

Gilt das auch für VS2005 und VS2008?
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Habe die beiden nicht mehr installiert … probier es aus und poste hier :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
jgl
Establishment
Beiträge: 109
Registriert: 08.04.2009, 08:58

Re: Is it just me, or …

Beitrag von jgl »

Würde ich gerne machen, aber wie weis ich nicht ;)
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Code: Alles auswählen

// Compile with /Ox /Ob2, link with /LTCG
#include <iostream>

struct Vector {
    float A, B, C, D;

    Vector(float A, float B, float C, float D)
        : A(A), B(B), C(C), D(D) { }

    Vector operator + (Vector const & Summand) {
        return Vector(A + Summand.A, B + Summand.B, C + Summand.C, D + Summand.D);
    }

    ~Vector() { }
};

int main() {

    float A, B, C, D;
    ::std::cin >> A >> B >> C >> D;
    Vector const Result = Vector(A, B, C, D) + Vector(0.0f, 1.0f, 2.0f, 3.0f);
    ::std::cout << Result.A << Result.B << Result.C << Result.D;

    return 0;
}
Release-Build (Full Optimization, Whole Program Optimization, Use Link-Time Code Generation), mit Strg+F10 in die Zeile mit dem ::std::cin springen, Rechtsklick -> View Disassembly. Wenn ein call Vector::operator + drinsteht, ist der Bug mit hoher Wahrscheinlichkeit auch dort vorhanden. Dann Debugging abbrechen, den Destruktor auskommentieren, neu builden, wieder in die Zeile springen, wieder Disassembly anschauen. Wenn die Addition komplett geinlined wurde und der Code insgesamt kürzer ist, haben wir einen Treffer. Oder, falls du mit Disassembly nicht viel anfangen kannst, den Assembler-Code beide Male rauskopieren und hier posten.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
jgl
Establishment
Beiträge: 109
Registriert: 08.04.2009, 08:58

Re: Is it just me, or …

Beitrag von jgl »

Okay, also so wie Du beschrieben hast (alle Compiler- und Linkereinstellung so gemacht), Code kopiert.

Also:
1) VS2005: dort taucht ein "call Vector::operator+ (401000h) " auf.
Ich poste mal das ganzen Disassembly

Code: Alles auswählen

int main() {
00401030  push        ebp  
00401031  mov         ebp,esp 
00401033  and         esp,0FFFFFFF8h 
00401036  sub         esp,40h 

    float A, B, C, D;
    ::std::cin >> A >> B >> C >> D;
00401039  lea         eax,[esp+0Ch] 
0040103D  push        eax  
0040103E  lea         ecx,[esp+0Ch] 
00401042  push        ecx  
00401043  mov         ecx,dword ptr [__imp_std::cin (402040h)] 
00401049  lea         edx,[esp+0Ch] 
0040104D  push        edx  
0040104E  lea         eax,[esp+0Ch] 
00401052  push        eax  
00401053  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (402044h)] 
00401059  mov         ecx,eax 
0040105B  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (402044h)] 
00401061  mov         ecx,eax 
00401063  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (402044h)] 
00401069  mov         ecx,eax 
0040106B  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (402044h)] 
    Vector const Result = Vector(A, B, C, D) + Vector(0.0f, 1.0f, 2.0f, 3.0f);
00401071  fldz             
00401073  fstp        dword ptr [esp+10h] 
00401077  lea         ecx,[esp+10h] 
0040107B  fld1             
0040107D  lea         eax,[esp+30h] 
00401081  fstp        dword ptr [esp+14h] 
00401085  lea         edx,[esp+20h] 
00401089  fld         dword ptr [__real@40000000 (402118h)] 
0040108F  fstp        dword ptr [esp+18h] 
00401093  fld         dword ptr [__real@40400000 (402114h)] 
00401099  fstp        dword ptr [esp+1Ch] 
0040109D  fld         dword ptr [esp] 
004010A0  fstp        dword ptr [esp+20h] 
004010A4  fld         dword ptr [esp+4] 
004010A8  fstp        dword ptr [esp+24h] 
004010AC  fld         dword ptr [esp+8] 
004010B0  fstp        dword ptr [esp+28h] 
004010B4  fld         dword ptr [esp+0Ch] 
004010B8  fstp        dword ptr [esp+2Ch] 
004010BC  call        Vector::operator+ (401000h) 
    ::std::cout << Result.A << Result.B << Result.C << Result.D;
004010C1  fld         dword ptr [esp+3Ch] 
004010C5  mov         ecx,dword ptr [__imp_std::cout (40203Ch)] 
004010CB  sub         esp,10h 
004010CE  fstp        dword ptr [esp+0Ch] 
004010D2  fld         dword ptr [esp+48h] 
004010D6  fstp        dword ptr [esp+8] 
004010DA  fld         dword ptr [esp+44h] 
004010DE  fstp        dword ptr [esp+4] 
004010E2  fld         dword ptr [esp+40h] 
004010E6  fstp        dword ptr [esp] 
004010E9  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)] 
004010EF  mov         ecx,eax 
004010F1  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)] 
004010F7  mov         ecx,eax 
004010F9  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)] 
004010FF  mov         ecx,eax 
00401101  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)] 

    return 0;
00401107  xor         eax,eax 
}
1) VS2008: dort taucht ein "call Vector::operator+ (401000h) " auf.
Ich poste mal wieden das ganzen Disassembly

Code: Alles auswählen

   
 int main() {
01131030  push        ebp  
01131031  mov         ebp,esp 
01131033  and         esp,0FFFFFFF8h 
01131036  sub         esp,40h 

        float A, B, C, D;
        ::std::cin >> A >> B >> C >> D;
01131039  lea         eax,[esp+0Ch] 
0113103D  push        eax  
0113103E  lea         ecx,[esp+0Ch] 
01131042  push        ecx  
01131043  mov         ecx,dword ptr [__imp_std::cin (1132040h)] 
01131049  lea         edx,[esp+0Ch] 
0113104D  push        edx  
0113104E  lea         eax,[esp+0Ch] 
01131052  push        eax  
01131053  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (1132038h)] 
01131059  mov         ecx,eax 
0113105B  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (1132038h)] 
01131061  mov         ecx,eax 
01131063  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (1132038h)] 
01131069  mov         ecx,eax 
0113106B  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (1132038h)] 
        Vector const Result = Vector(A, B, C, D) + Vector(0.0f, 1.0f, 2.0f, 3.0f);
01131071  fldz             
01131073  fstp        dword ptr [esp+10h] 
01131077  lea         ecx,[esp+10h] 
0113107B  fld1             
0113107D  lea         eax,[esp+30h] 
01131081  fstp        dword ptr [esp+14h] 
01131085  lea         edx,[esp+20h] 
01131089  fld         dword ptr [__real@40000000 (1132118h)] 
0113108F  fstp        dword ptr [esp+18h] 
01131093  fld         dword ptr [__real@40400000 (1132114h)] 
01131099  fstp        dword ptr [esp+1Ch] 
0113109D  fld         dword ptr [esp] 
011310A0  fstp        dword ptr [esp+20h] 
011310A4  fld         dword ptr [esp+4] 
011310A8  fstp        dword ptr [esp+24h] 
011310AC  fld         dword ptr [esp+8] 
011310B0  fstp        dword ptr [esp+28h] 
011310B4  fld         dword ptr [esp+0Ch] 
011310B8  fstp        dword ptr [esp+2Ch] 
011310BC  call        Vector::operator+ (1131000h) 
        ::std::cout << Result.A << Result.B << Result.C << Result.D;
011310C1  fld         dword ptr [esp+3Ch] 
011310C5  mov         ecx,dword ptr [__imp_std::cout (1132044h)] 
011310CB  sub         esp,10h 
011310CE  fstp        dword ptr [esp+0Ch] 
011310D2  fld         dword ptr [esp+48h] 
011310D6  fstp        dword ptr [esp+8] 
011310DA  fld         dword ptr [esp+44h] 
011310DE  fstp        dword ptr [esp+4] 
011310E2  fld         dword ptr [esp+40h] 
011310E6  fstp        dword ptr [esp] 
011310E9  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (113203Ch)] 
011310EF  mov         ecx,eax 
011310F1  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (113203Ch)] 
011310F7  mov         ecx,eax 
011310F9  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (113203Ch)] 
011310FF  mov         ecx,eax 
01131101  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (113203Ch)] 

        return 0;
01131107  xor         eax,eax 
    }
Und nun habe ich den leeren Destructor rausgeschmissen und es steht auch kein " call Vector::operator+" mehr.
Kommt mir auch kürzer vor :)
Also demnach existiert dieses Problem schon seit VS2005...
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Super. Damit wäre das reproduziert :) Falls du bei MSDN Connect bist, kannst du das Ticket ja bestätigen/uppen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
jgl
Establishment
Beiträge: 109
Registriert: 08.04.2009, 08:58

Re: Is it just me, or …

Beitrag von jgl »

Destructor nun auskommentiert.

Disassembly VS 2005:

Code: Alles auswählen

int main() {
00401000  sub         esp,20h 

    float A, B, C, D;
    ::std::cin >> A >> B >> C >> D;
00401003  lea         eax,[esp+0Ch] 
00401007  push        eax  
00401008  lea         ecx,[esp+0Ch] 
0040100C  push        ecx  
0040100D  mov         ecx,dword ptr [__imp_std::cin (402040h)] 
00401013  lea         edx,[esp+0Ch] 
00401017  push        edx  
00401018  lea         eax,[esp+0Ch] 
0040101C  push        eax  
0040101D  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (402044h)] 
00401023  mov         ecx,eax 
00401025  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (402044h)] 
0040102B  mov         ecx,eax 
0040102D  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (402044h)] 
00401033  mov         ecx,eax 
00401035  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (402044h)] 
    Vector const Result = Vector(A, B, C, D) + Vector(0.0f, 1.0f, 2.0f, 3.0f);
0040103B  fld         dword ptr [esp] 
0040103E  fadd        qword ptr [__real@0000000000000000 (402130h)] 
    ::std::cout << Result.A << Result.B << Result.C << Result.D;
00401044  mov         ecx,dword ptr [__imp_std::cout (40203Ch)] 
0040104A  sub         esp,10h 
0040104D  fstp        dword ptr [esp+20h] 
00401051  fld         dword ptr [esp+14h] 
00401055  fadd        qword ptr [__real@3ff0000000000000 (402128h)] 
0040105B  fstp        dword ptr [esp+24h] 
0040105F  fld         dword ptr [esp+18h] 
00401063  fadd        qword ptr [__real@4000000000000000 (402120h)] 
00401069  fstp        dword ptr [esp+28h] 
0040106D  fld         dword ptr [esp+1Ch] 
00401071  fadd        qword ptr [__real@4008000000000000 (402118h)] 
00401077  fstp        dword ptr [esp+2Ch] 
0040107B  fld         dword ptr [esp+2Ch] 
0040107F  fstp        dword ptr [esp+0Ch] 
00401083  fld         dword ptr [esp+28h] 
00401087  fstp        dword ptr [esp+8] 
0040108B  fld         dword ptr [esp+24h] 
0040108F  fstp        dword ptr [esp+4] 
00401093  fld         dword ptr [esp+20h] 
00401097  fstp        dword ptr [esp] 
0040109A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)] 
004010A0  mov         ecx,eax 
004010A2  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)] 
004010A8  mov         ecx,eax 
004010AA  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)] 
004010B0  mov         ecx,eax 
004010B2  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (402038h)] 

    return 0;
004010B8  xor         eax,eax 
}
Disassembly VS 2008:

Code: Alles auswählen

    int main() {
00181000  sub         esp,20h 

        float A, B, C, D;
        ::std::cin >> A >> B >> C >> D;
00181003  lea         eax,[esp+0Ch] 
00181007  push        eax  
00181008  lea         ecx,[esp+0Ch] 
0018100C  push        ecx  
0018100D  mov         ecx,dword ptr [__imp_std::cin (182040h)] 
00181013  lea         edx,[esp+0Ch] 
00181017  push        edx  
00181018  lea         eax,[esp+0Ch] 
0018101C  push        eax  
0018101D  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (182038h)] 
00181023  mov         ecx,eax 
00181025  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (182038h)] 
0018102B  mov         ecx,eax 
0018102D  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (182038h)] 
00181033  mov         ecx,eax 
00181035  call        dword ptr [__imp_std::basic_istream<char,std::char_traits<char> >::operator>> (182038h)] 
        Vector const Result = Vector(A, B, C, D) + Vector(0.0f, 1.0f, 2.0f, 3.0f);
0018103B  fld         dword ptr [esp] 
0018103E  fadd        qword ptr [__real@0000000000000000 (182130h)] 
        ::std::cout << Result.A << Result.B << Result.C << Result.D;
00181044  mov         ecx,dword ptr [__imp_std::cout (182044h)] 
0018104A  sub         esp,10h 
0018104D  fstp        dword ptr [esp+20h] 
00181051  fld         dword ptr [esp+14h] 
00181055  fadd        qword ptr [__real@3ff0000000000000 (182128h)] 
0018105B  fstp        dword ptr [esp+24h] 
0018105F  fld         dword ptr [esp+18h] 
00181063  fadd        qword ptr [__real@4000000000000000 (182120h)] 
00181069  fstp        dword ptr [esp+28h] 
0018106D  fld         dword ptr [esp+1Ch] 
00181071  fadd        qword ptr [__real@4008000000000000 (182118h)] 
00181077  fstp        dword ptr [esp+2Ch] 
0018107B  fld         dword ptr [esp+2Ch] 
0018107F  fstp        dword ptr [esp+0Ch] 
00181083  fld         dword ptr [esp+28h] 
00181087  fstp        dword ptr [esp+8] 
0018108B  fld         dword ptr [esp+24h] 
0018108F  fstp        dword ptr [esp+4] 
00181093  fld         dword ptr [esp+20h] 
00181097  fstp        dword ptr [esp] 
0018109A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (18203Ch)] 
001810A0  mov         ecx,eax 
001810A2  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (18203Ch)] 
001810A8  mov         ecx,eax 
001810AA  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (18203Ch)] 
001810B0  mov         ecx,eax 
001810B2  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (18203Ch)] 

        return 0;
001810B8  xor         eax,eax 
    }
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Mal ein kleines Update:

Bei MS konnte der Bug zuerst nicht reproduziert werden. Ich musste nochmal ran und habe dann herausgefunden, dass Exception-Handling aktiviert sein muss, damit das Verhalten auftritt …

… weiter vorne im Thread habe ich mich ja schon darüber aufgeregt, dass Funktionen, die try-Blöcke enthalten, nicht geinlined werden können. Aus dieser Zeit – besser gesagt aus der Beschreibung der Warnung C4717 – hatte ich noch im Hinterkopf, dass Funktionen nicht geinlined werden können, wenn ihre Rückgabewerte unwindable sind – also im Fall einer Exception ein Destruktor aufgerufen werden müsste.

Ich schätze die Situation so ein, dass der Compiler in dem Code-Beispiel Vector als unwindable klassifiziert, sobald man einen Destruktor – wenn auch nur einen leeren – definiert, und es deshalb ablehnt, Vector::operator + () zu inlinen.

Das alles wäre nicht merkwürdig, wenn der Compiler nicht normalerweise großartige Arbeit bei solchen Klassifizierungen leisten würde. Dass all die Mechanismen, die sonst bestimmen ob eine Funktion wegoptimiert werden kann und ob sie Exception-Handling benötigt, bei einem manuell definierten Destruktor versagen ist vielleicht keinen Bug-Report mehr wert, aber zumindest einen Verbesserungsvorschlag. Jetzt warte ich aber erstmal ab, was das VC-Team dazu sagt, vielleicht bin ich ja auch auf dem Holzweg.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
eXile
Establishment
Beiträge: 1136
Registriert: 28.02.2009, 13:27

Re: Is it just me, or …

Beitrag von eXile »

Hier mal wieder etwas aus der Abteilung "merkwürdiges Verhalten": http://ompf.org/forum/viewtopic.php?t=1744&p=19145

Und wir sehen: Ohne __forceinline wird der Code geinlined und die überflüssigen Konstruktoren werden verworfen …
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Interessant … was es nicht alles gibt. Ich schätze, der Initializer des Arrays wird nur aus bestimmten Stellen rausoptimiert … ich werde direkt mal prüfen, ob ich irgendwo uninitialisierte Arrays habe.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8267
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Krishty »

Hmmm, der Compiler scheint auch nicht daraufhin zu optimieren, dass durch Datentypgrenzen bestimmte Vergleiche konstant sind:

Code: Alles auswählen

bool CheckCharacter(char x) { // default char signed
    return (48 <= x) && (127 >= x);
}
Hier wird der Compiler beide Vergleiche durchführen, obwohl der zweite Vergleich durch das char-Intervall von [-128, 127] immer true liefert.

Immer, wenn eine Bereichsgrenze auf 0, 127, 32767 oder 2^31-1 liegt, kann man durch den richtigen Datentypen einen Vergleich sparen. Leider muss man das scheinbar manuell machen. Oder gibt es einen guten Grund, warum der Compiler das nicht optimiert?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
TGGC
Establishment
Beiträge: 569
Registriert: 15.05.2009, 18:14
Benutzertext: Ich _bin_ es.
Alter Benutzername: TGGC
Echter Name: Ich _bin_ es.
Wohnort: Mainz
Kontaktdaten:

Re: Is it just me, or …

Beitrag von TGGC »

Krishty hat geschrieben:Hmmm, der Compiler scheint auch nicht daraufhin zu optimieren, dass durch Datentypgrenzen bestimmte Vergleiche konstant sind:

Code: Alles auswählen

bool CheckCharacter(char x) { // default char signed
    return (48 <= x) && (127 >= x);
}
Hier wird der Compiler beide Vergleiche durchführen, obwohl der zweite Vergleich durch das char-Intervall von [-128, 127] immer true liefert.

Immer, wenn eine Bereichsgrenze auf 0, 127, 32767 oder 2^31-1 liegt, kann man durch den richtigen Datentypen einen Vergleich sparen. Leider muss man das scheinbar manuell machen. Oder gibt es einen guten Grund, warum der Compiler das nicht optimiert?
Nein, der zweite Vergleich ist nicht immer wahr, sondern nur wenn x >= 127 ist. f'`8k


Gruß, TGGC (der kostenlose DMC Download)
Alexander Kornrumpf
Moderator
Beiträge: 2119
Registriert: 25.02.2009, 13:37

Re: Is it just me, or …

Beitrag von Alexander Kornrumpf »

TGGC: Du hast die richtung des Vergleichs umgedreht.

Krishty: Mal ne ganz platte Vermutung: Es kommt extrem selten vor dass jemand einen integralwert mit einer konstanten an der Grenze des Wertebereichs vergleicht und lohnt sich daher nicht.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4878
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Schrompf »

Nein, TGGC hat schon recht: "127 >= x" bedeutet eben "größer oder gleich". Und char kann nunmal einen Wert von 127 annehmen. Die Bedingung ist also nicht zwangsweise falsch.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Alexander Kornrumpf
Moderator
Beiträge: 2119
Registriert: 25.02.2009, 13:37

Re: Is it just me, or …

Beitrag von Alexander Kornrumpf »

Thomas:
Krishty: 127 >= x oder auch x <=127

TGGC: x >= 127

ist schon ein Unterschied.

P.S. Genau deswegen ist konstanten nach vorne schreiben unituitiv.
P.P.S. Was soll überhaupt das dämliche = dort? 128 > x hätte es doch auch getan. Vielleicht kapiert der compiler das sogar besser.
Benutzeravatar
Schrompf
Moderator
Beiträge: 4878
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Is it just me, or …

Beitrag von Schrompf »

Oh stimmt, hab mich von der verworrenen Schreibweise verwirren lassen.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
Antworten