Keine Ahnung was ich gemacht hab :D , aber ich hab hier ein sehr langes Textfile mit Bugs, für die ich keine Zeit hatte, einen anständigen Report zu schreiben. Mal sehen, vielleicht machen wir demnächst weitere Experimente... :twisted:Schrompf hat geschrieben:bringst Du dort regelmäßig Kuchen vorbei? :-)
Sammelthread zu Visual C++’ Compiler
- dot
- Establishment
- Beiträge: 1745
- Registriert: 06.03.2004, 18:10
- Echter Name: Michael Kenzel
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Ich bin ein kleines Bisschen irritiert :D Ja los, schaufel alles rein …
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Aktualisierung auf Visual Studio 15.7.5.
Mit diesem Update wurde ein Haufen Bugs behoben – wirklich eine riesen Menge.
Ansonsten hat sich am Compiler kaum was getan.
Der 32-Bit-Compiler nutzte bei Optimierung auf Größe bisweilen für Ausdrücke wie x - 20 Befehle wie lea eax, eax-20. Nun nutzt er öfter sub eax, 20. Das erzeugt höheren Registerdruck und produziert längere Befehlsketten. Ich denke, dass das eine Regression ist, denn ein vernünftiger Grund fällt mir nicht ein (lea war perfekt dafür).
Der 64-Bit-Compiler fasst Sprünge weniger aggressiv zusammen, wenn auf Geschwindigkeit optimiert wird:
if(…) return 0; else return var;
kompiliert nun zu
xor eax,eax
add rsp,70h
pop r15
pop r14
pop r13
pop r12
pop rdi
pop rsi
pop rbx
ret
mov rax,qword ptr [rax+8]
add rsp,70h
pop r15
pop r14
pop r13
pop r12
pop rdi
pop rsi
pop rbx
ret
Vorher wurde das zusammengefasst.
Insgesamt sind die Leistungsänderungen kaum messbar. Alle meine Projekte zusammen haben sich weniger als 300 B zum Schlechtne verändert – bei einigen Megabyte Gesamtgröße. Dafür, wie gesagt, haufenweise Bugfixes.
Mit diesem Update wurde ein Haufen Bugs behoben – wirklich eine riesen Menge.
Ansonsten hat sich am Compiler kaum was getan.
Der 32-Bit-Compiler nutzte bei Optimierung auf Größe bisweilen für Ausdrücke wie x - 20 Befehle wie lea eax, eax-20. Nun nutzt er öfter sub eax, 20. Das erzeugt höheren Registerdruck und produziert längere Befehlsketten. Ich denke, dass das eine Regression ist, denn ein vernünftiger Grund fällt mir nicht ein (lea war perfekt dafür).
Der 64-Bit-Compiler fasst Sprünge weniger aggressiv zusammen, wenn auf Geschwindigkeit optimiert wird:
if(…) return 0; else return var;
kompiliert nun zu
xor eax,eax
add rsp,70h
pop r15
pop r14
pop r13
pop r12
pop rdi
pop rsi
pop rbx
ret
mov rax,qword ptr [rax+8]
add rsp,70h
pop r15
pop r14
pop r13
pop r12
pop rdi
pop rsi
pop rbx
ret
Vorher wurde das zusammengefasst.
Insgesamt sind die Leistungsänderungen kaum messbar. Alle meine Projekte zusammen haben sich weniger als 300 B zum Schlechtne verändert – bei einigen Megabyte Gesamtgröße. Dafür, wie gesagt, haufenweise Bugfixes.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Aktualisierung auf 15.8.0.
Die Code Generation nutzt eine neue Version der Static Single Assignment Form. Ich habe bisher keine offizielle Stellungnahme gefunden, aber ein Mitglied des DirectX-Teams schreibt es.
Eine erste Sichtung bestätigt das: Der Code meiner Projekte hat sich drastisch verändert. Auf den ersten Blick würde ich sagen: Unter x86-32 gibt es fantastische Verbesserungen und unter x86-64 fatale Verschlechterungen.
Unter x86-32 sind vor allem Funktionen betroffen, die aus etlichen else if oder switch mit unzähligen case bestehen. Ich beobachte hier teils 20 % weniger Befehle(!) und falls sich das bestätigt, ist das echt sensationell. In die augenscheinlichen Verschlechterungen von x64 habe ich noch nicht reingeschaut.
Mehr dazu später.
Die Code Generation nutzt eine neue Version der Static Single Assignment Form. Ich habe bisher keine offizielle Stellungnahme gefunden, aber ein Mitglied des DirectX-Teams schreibt es.
Eine erste Sichtung bestätigt das: Der Code meiner Projekte hat sich drastisch verändert. Auf den ersten Blick würde ich sagen: Unter x86-32 gibt es fantastische Verbesserungen und unter x86-64 fatale Verschlechterungen.
Unter x86-32 sind vor allem Funktionen betroffen, die aus etlichen else if oder switch mit unzähligen case bestehen. Ich beobachte hier teils 20 % weniger Befehle(!) und falls sich das bestätigt, ist das echt sensationell. In die augenscheinlichen Verschlechterungen von x64 habe ich noch nicht reingeschaut.
Mehr dazu später.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
x86-32 mit Optimierung auf Größe:
Ich habe mir nun mal das Disassembly einer Funktion angeschaut, die von 1483 B auf 1131 B geschrumpft ist (sage und schreibe 24 %!) …
Optimiert wurden keinesfalls triviale switch-Konstruktionen à:
switch(param) {
case 0: return "foo";
case 1: return "bar";
case 2: …
… die waren auch vorher schon ziemlich optimal. Nein, stattdessen werden jetzt verschiedene Unterzweige zusammengefasst. Sagen wir etwa:
char buf[…];
switch(errorType) {
case CError:
strcpy(buf, strerr(errno));
break;
case CppException:
strcpy(buf, exceptionObject.str());
break;
default:
strcpy(buf, "unknown");
break;
Obwohl die drei Pfade in der SSA drastisch unterschiedlich sind (einer erzeugt einen C++-String, dar andere nicht, und der dritte läuft komplett über Konstanten) wird bei mir nun das strcpy() zusammengefasst. Wenn man bedenkt, dass es zuvor völlig unterschiedliche Register und völlig unterschiedliche Stack-Quellen benutzt hat, ist das schon recht beeindruckend. Ich sehe sogar Code, der bei Strings die Null am Ende mitkopiert, mit Code zusammengefaltet, der sie überspringt, wenn genug überlappende Befehle existieren. Es fällt mir zunehmend schwer, da etwas Negatives festzustellen.
x86-64 mit Optimierung auf Größe:
Sprach ich von Negativem? Ach ja, die 64-Bit-Version. Dieser Code:
auto end = copyUntilZero(message, "D3D info: GPU supports Direct3D ");
switch(USize(more)) {
case 0x9100: copyZeroTerminated(end, "9" ); break;
case 0x9200: copyZeroTerminated(end, "9.0b"); break;
case 0x9300: copyZeroTerminated(end, "9.0c"); break;
case 0xA000: copyZeroTerminated(end, "10" ); break;
case 0xA100: copyZeroTerminated(end, "10.1"); break;
case 0xB000: copyZeroTerminated(end, "11" ); break;
case 0xB100: copyZeroTerminated(end, "11.1"); break;
case 0xB200: copyZeroTerminated(end, "11.2"); break;
case 0xB300: copyZeroTerminated(end, "11.3"); break;
case 0xB400: copyZeroTerminated(end, "11.4"); break;
case 0xC000: copyZeroTerminated(end, "12" ); break;
case 0xC100: copyZeroTerminated(end, "12.1"); break;
default: UNREACHABLE;
}
… sieht auf x86 für Größe perfekt aus:
53 Befehle, und dazu recht kompakte. Jetzt die x64-Version mit den selben Einstellungen, ebenfalls für Größe(!) optimiert:
140 Befehle. Und alles Duplikate. Über drei Mal so groß wie die 32-Bit-Version. DREI FUCKING MAL. Ohne Grund, einfach because FUCK YOU, that’s why. I can’t even …
Wirklich, vergesst den 64-Bit-Compiler, wenn ihr nach Größe geht. Und das ist so eine riesen Verschwendung, denn theoretisch könnte die 64-Bit-Version durch geringeren Registerdruck sogar überlegenen Code produzieren. Ich habe das Gefühl, dass das noch nie jemand bei Microsoft getestet hat und wenn ich’s isoliert kriege, wird das ein fetter Bug Report.
Davon ab hat sich mal wieder die Gewichtung für Inlining geändert. Das hatten wir doch letztens erst. Warum mit jedem Release? Und es wird irgendwie auch überhaupt nicht besser dadurch …
… okay, auch mal was Positives: Die Register Allocation hat sich wirklich verbessert. In der Größenordnung von 1 % sogar, also deutlich über dem Rauschen, in dem die letzten Verbesserungen untergingen. Ich sehe weniger MOV und weniger Registerdruck gerade bei vielen Integer-Operationen. Sehr vereinzelt, aber in der Summe mit stattlichem Ergebnis.
x86 und x64 für Geschwindigkeit:
Habe leider gerade keine Benchmarks parat. Die Größe der Executables ist jedenfalls leicht gesunken – viel weniger Befehle (wahrscheinlich die gleichen SSA-Verbesserungen wie bei der Optimierung für Größe), aber dafür mehr statische Daten (?!?). Hmm.
Ich habe mir nun mal das Disassembly einer Funktion angeschaut, die von 1483 B auf 1131 B geschrumpft ist (sage und schreibe 24 %!) …
Optimiert wurden keinesfalls triviale switch-Konstruktionen à:
switch(param) {
case 0: return "foo";
case 1: return "bar";
case 2: …
… die waren auch vorher schon ziemlich optimal. Nein, stattdessen werden jetzt verschiedene Unterzweige zusammengefasst. Sagen wir etwa:
char buf[…];
switch(errorType) {
case CError:
strcpy(buf, strerr(errno));
break;
case CppException:
strcpy(buf, exceptionObject.str());
break;
default:
strcpy(buf, "unknown");
break;
Obwohl die drei Pfade in der SSA drastisch unterschiedlich sind (einer erzeugt einen C++-String, dar andere nicht, und der dritte läuft komplett über Konstanten) wird bei mir nun das strcpy() zusammengefasst. Wenn man bedenkt, dass es zuvor völlig unterschiedliche Register und völlig unterschiedliche Stack-Quellen benutzt hat, ist das schon recht beeindruckend. Ich sehe sogar Code, der bei Strings die Null am Ende mitkopiert, mit Code zusammengefaltet, der sie überspringt, wenn genug überlappende Befehle existieren. Es fällt mir zunehmend schwer, da etwas Negatives festzustellen.
x86-64 mit Optimierung auf Größe:
Sprach ich von Negativem? Ach ja, die 64-Bit-Version. Dieser Code:
auto end = copyUntilZero(message, "D3D info: GPU supports Direct3D ");
switch(USize(more)) {
case 0x9100: copyZeroTerminated(end, "9" ); break;
case 0x9200: copyZeroTerminated(end, "9.0b"); break;
case 0x9300: copyZeroTerminated(end, "9.0c"); break;
case 0xA000: copyZeroTerminated(end, "10" ); break;
case 0xA100: copyZeroTerminated(end, "10.1"); break;
case 0xB000: copyZeroTerminated(end, "11" ); break;
case 0xB100: copyZeroTerminated(end, "11.1"); break;
case 0xB200: copyZeroTerminated(end, "11.2"); break;
case 0xB300: copyZeroTerminated(end, "11.3"); break;
case 0xB400: copyZeroTerminated(end, "11.4"); break;
case 0xC000: copyZeroTerminated(end, "12" ); break;
case 0xC100: copyZeroTerminated(end, "12.1"); break;
default: UNREACHABLE;
}
… sieht auf x86 für Größe perfekt aus:
Code: Alles auswählen
mov edx,offset string "D3D info: GPU supports Direct3D@"... (01C1E0h)
lea ecx,[message]
mov al,44h
cbw
mov word ptr [ecx],ax
add ecx,2
inc edx
mov al,byte ptr [edx]
test al,al
jne UTF16Supervisor::forwardEventAsUTF16+2FEh (015E35h)
mov eax,0B100h
cmp edi,eax
ja UTF16Supervisor::forwardEventAsUTF16+382h (015EB9h)
je UTF16Supervisor::forwardEventAsUTF16+378h (015EAFh)
cmp edi,9100h
je UTF16Supervisor::forwardEventAsUTF16+36Eh (015EA5h)
cmp edi,9200h
je UTF16Supervisor::forwardEventAsUTF16+364h (015E9Bh)
cmp edi,9300h
je UTF16Supervisor::forwardEventAsUTF16+35Ah (015E91h)
cmp edi,0A000h
je UTF16Supervisor::forwardEventAsUTF16+350h (015E87h)
mov edx,offset string "11" (01C224h)
mov eax,offset string "10.1" (01C21Ch)
cmp edi,0A100h
cmove edx,eax
jmp UTF16Supervisor::forwardEventAsUTF16+442h (015F79h)
mov edx,offset string "10" (01C218h)
jmp UTF16Supervisor::forwardEventAsUTF16+442h (015F79h)
mov edx,offset string "9.0c" (01C210h)
jmp UTF16Supervisor::forwardEventAsUTF16+442h (015F79h)
mov edx,offset string "9.0b" (01C208h)
jmp UTF16Supervisor::forwardEventAsUTF16+442h (015F79h)
mov edx,offset string "9" (01C204h)
jmp UTF16Supervisor::forwardEventAsUTF16+442h (015F79h)
mov edx,offset string "11.1" (01C228h)
jmp UTF16Supervisor::forwardEventAsUTF16+442h (015F79h)
cmp edi,0B200h
je UTF16Supervisor::forwardEventAsUTF16+3C0h (015EF7h)
cmp edi,0B300h
je UTF16Supervisor::forwardEventAsUTF16+3B6h (015EEDh)
cmp edi,0B400h
je UTF16Supervisor::forwardEventAsUTF16+3ACh (015EE3h)
mov edx,offset string "12.1" (01C24Ch)
mov eax,offset string "12" (01C248h)
cmp edi,0C000h
jmp UTF16Supervisor::forwardEventAsUTF16+348h (015E7Fh)
mov edx,offset string "11.4" (01C240h)
jmp UTF16Supervisor::forwardEventAsUTF16+442h (015F79h)
mov edx,offset string "11.3" (01C238h)
jmp UTF16Supervisor::forwardEventAsUTF16+442h (015F79h)
mov edx,offset string "11.2" (01C230h)
Code: Alles auswählen
lea rdx,[string "D3D info: GPU supports Direct3D@"... (01E530h)]
mov al,44h
lea rcx,[message]
inc rdx
movsx eax,al
mov word ptr [rcx],ax
add rcx,2
mov al,byte ptr [rdx]
test al,al
jne UTF16Supervisor::forwardEventAsUTF16+3DFh (0168A7h)
mov eax,0B100h
cmp r8,rax
ja UTF16Supervisor::forwardEventAsUTF16+535h (0169FDh)
je UTF16Supervisor::forwardEventAsUTF16+50Fh (0169D7h)
cmp r8,9100h
je UTF16Supervisor::forwardEventAsUTF16+4E9h (0169B1h)
cmp r8,9200h
je UTF16Supervisor::forwardEventAsUTF16+4C3h (01698Bh)
cmp r8,9300h
je UTF16Supervisor::forwardEventAsUTF16+49Dh (016965h)
mov eax,31h
mov word ptr [rcx],ax
cmp r8,0A000h
je UTF16Supervisor::forwardEventAsUTF16+47Fh (016947h)
cmp r8,0A100h
je UTF16Supervisor::forwardEventAsUTF16+461h (016929h)
lea rdx,[string "11" (01E574h)]
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+44Ah (016912h)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
lea rdx,[string "10.1" (01E56Ch)]
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+468h (016930h)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
lea rdx,[string "10" (01E568h)]
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+486h (01694Eh)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
mov eax,39h
lea rdx,[string "9.0c" (01E560h)]
mov word ptr [rcx],ax
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+4ACh (016974h)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
mov eax,39h
lea rdx,[string "9.0b" (01E558h)]
mov word ptr [rcx],ax
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+4D2h (01699Ah)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
mov eax,39h
lea rdx,[string "9" (01E554h)]
mov word ptr [rcx],ax
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+4F8h (0169C0h)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
mov eax,31h
lea rdx,[string "11.1" (01E578h)]
mov word ptr [rcx],ax
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+51Eh (0169E6h)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
mov eax,31h
mov word ptr [rcx],ax
cmp r8,0B200h
je UTF16Supervisor::forwardEventAsUTF16+5DDh (016AA5h)
cmp r8,0B300h
je UTF16Supervisor::forwardEventAsUTF16+5BFh (016A87h)
cmp r8,0B400h
je UTF16Supervisor::forwardEventAsUTF16+5A1h (016A69h)
cmp r8,0C000h
je UTF16Supervisor::forwardEventAsUTF16+583h (016A4Bh)
lea rdx,[string "12.1" (01E59Ch)]
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+56Ch (016A34h)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
lea rdx,[string "12" (01E598h)]
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+58Ah (016A52h)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
lea rdx,[string "11.4" (01E590h)]
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+5A8h (016A70h)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
lea rdx,[string "11.3" (01E588h)]
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+5C6h (016A8Eh)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
lea rdx,[string "11.2" (01E580h)]
inc rdx
lea rcx,[rcx+2]
movsx eax,byte ptr [rdx]
mov word ptr [rcx],ax
cmp byte ptr [rdx],r14b
jne UTF16Supervisor::forwardEventAsUTF16+5E4h (016AACh)
jmp UTF16Supervisor::forwardEventAsUTF16+691h (016B59h)
Wirklich, vergesst den 64-Bit-Compiler, wenn ihr nach Größe geht. Und das ist so eine riesen Verschwendung, denn theoretisch könnte die 64-Bit-Version durch geringeren Registerdruck sogar überlegenen Code produzieren. Ich habe das Gefühl, dass das noch nie jemand bei Microsoft getestet hat und wenn ich’s isoliert kriege, wird das ein fetter Bug Report.
Davon ab hat sich mal wieder die Gewichtung für Inlining geändert. Das hatten wir doch letztens erst. Warum mit jedem Release? Und es wird irgendwie auch überhaupt nicht besser dadurch …
… okay, auch mal was Positives: Die Register Allocation hat sich wirklich verbessert. In der Größenordnung von 1 % sogar, also deutlich über dem Rauschen, in dem die letzten Verbesserungen untergingen. Ich sehe weniger MOV und weniger Registerdruck gerade bei vielen Integer-Operationen. Sehr vereinzelt, aber in der Summe mit stattlichem Ergebnis.
x86 und x64 für Geschwindigkeit:
Habe leider gerade keine Benchmarks parat. Die Größe der Executables ist jedenfalls leicht gesunken – viel weniger Befehle (wahrscheinlich die gleichen SSA-Verbesserungen wie bei der Optimierung für Größe), aber dafür mehr statische Daten (?!?). Hmm.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Bug Report für die absurde Größe auf x64 ist raus: https://developercommunity.visualstudio ... r-siz.html
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Wieder was gelernt: Wenn die Funktionen zu groß werden, schaltet sich Visual C++’ Optimizer einfach ab. Mit /d2OptimizeHugeFunctions als zusätzlichem Parameter zwingt man ihn.
In meinen Projekten habe ich so eine Funktionsgröße nie erreicht, und auch bei Assimp konnte ich das jetzt nicht reproduzieren. Aber wer mit sowas zu tun hat, weiß nun bescheid.
In meinen Projekten habe ich so eine Funktionsgröße nie erreicht, und auch bei Assimp konnte ich das jetzt nicht reproduzieren. Aber wer mit sowas zu tun hat, weiß nun bescheid.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Wenn ihr z.B. float zu int konvertiert, ist reinterpret_cast undefiniertes Verhalten (obwohl Visual C++ es frisst) und union ebenfalls (obwohl GCC/Clang es fressen). Um standardkonform zu sein, solltet ihr das via memcpy() machen (ist übrigens auch in allen aktuellen Compilern die am besten optimierte Methode).
Nun, falls ihr das auch bei anderen Datentyp-Aliasing-Problemen so macht, habe ich schlechte Nachrichten für euch …
Nun, falls ihr das auch bei anderen Datentyp-Aliasing-Problemen so macht, habe ich schlechte Nachrichten für euch …
Äh, wartet. Euer Compiler ist auf .NET gebaut?!The bug occurs in code as simple as this:
unsigned char A[2] = {0xAB, 0xCD};
int target = -1;
memcpy(&target, A, 2);
printf("%x\n", target);
When targeting x64, compiling without optimizations yields ffffcdab. Compiling with optimizations yields cdab. We appear to be giving the wrong address to the cpblk .NET instruction.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Wieder was Interessantes: __builtin_nan().
Ausgangslage: C++ hat std::numeric_limits<double>::quiet_nan() & Co. Damit bekommt man NaN, Infinity, usw zurückgeliefert.
Weiterhin: C++ erfordert, dass diese Funktionen constexpr sind.
Außerdem: C++ verbietet NaN & Co. in constexpr.
Merkt ihr den Widerspruch? Alle klassischen Möglichkeiten, einer Variable NaN zuzuweisen, schlagen mit constexpr fehl:
constexpr double nan = 1.0 / 0.0; // Visual C++: error C2131: expression did not evaluate to a constant note: failure was caused by an undefined arithmetic operation
constexpr double nan = 1.7976931348623157e308 * 2 * 0; // GCC: error: is not a constant expression
Aufgelöst werden kann das nur durch Compiler-interne Konstanten, auf die die Standardbibliothek für std::numeric_limits zurückgreift.
Bei GCC ist das z. B. __builtin_nan(). Und bei Visual C++ … seit neuestem ebenso:
_NODISCARD static constexpr double quiet_NaN() noexcept
{ // return non-signaling NaN
return (__builtin_nan("0"));
}
Wer nun glaubt, __builtin_nan("0") direkt nutzen zu können, irrt:
constexpr nan = __builtin_nan("0"); // error C2143: syntax error: missing ')' before ';'
Aus irgendwelchen Compiler-internen Gründen darf die Funktion nur aus einer anderen Funktion aufgerufen werden:
constexpr double make_nan() {
return __builtin_nan("0");
}
constexpr double nan = make_nan();
Das ist … exotisch. Wisst’r bescheid. Ich hoffe nun darauf, dass Microsoft das Builtin weiter entwickelt, so dass ich den Quelltext für VC++ und Clang/GCC irgendwann mal vereinigen kann …
Ausgangslage: C++ hat std::numeric_limits<double>::quiet_nan() & Co. Damit bekommt man NaN, Infinity, usw zurückgeliefert.
Weiterhin: C++ erfordert, dass diese Funktionen constexpr sind.
Außerdem: C++ verbietet NaN & Co. in constexpr.
Merkt ihr den Widerspruch? Alle klassischen Möglichkeiten, einer Variable NaN zuzuweisen, schlagen mit constexpr fehl:
constexpr double nan = 1.0 / 0.0; // Visual C++: error C2131: expression did not evaluate to a constant note: failure was caused by an undefined arithmetic operation
constexpr double nan = 1.7976931348623157e308 * 2 * 0; // GCC: error: is not a constant expression
Aufgelöst werden kann das nur durch Compiler-interne Konstanten, auf die die Standardbibliothek für std::numeric_limits zurückgreift.
Bei GCC ist das z. B. __builtin_nan(). Und bei Visual C++ … seit neuestem ebenso:
_NODISCARD static constexpr double quiet_NaN() noexcept
{ // return non-signaling NaN
return (__builtin_nan("0"));
}
Wer nun glaubt, __builtin_nan("0") direkt nutzen zu können, irrt:
constexpr nan = __builtin_nan("0"); // error C2143: syntax error: missing ')' before ';'
Aus irgendwelchen Compiler-internen Gründen darf die Funktion nur aus einer anderen Funktion aufgerufen werden:
constexpr double make_nan() {
return __builtin_nan("0");
}
constexpr double nan = make_nan();
Das ist … exotisch. Wisst’r bescheid. Ich hoffe nun darauf, dass Microsoft das Builtin weiter entwickelt, so dass ich den Quelltext für VC++ und Clang/GCC irgendwann mal vereinigen kann …
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Aktualisierung von Visual C++ 2017.8.7 auf 2017.8.9 … keine Änderungen am C++-Compiler.
Warum ich’s dann überhaupt installiert habe? Weil geschrieben wurde, dass Visual C++ 2018.9 große Verbesserungen in der Template-Konformität bringt: Use the official range-v3 with MSVC 2017 version 15.9
Der Artikel spornt zum Ausprobieren an, aber leider ist 2017.9 noch gar nicht released. Und im kack Visual Studio Installer sieht man ja immer erst nach dem Update, welche Version man kriegt.
Warum ich’s dann überhaupt installiert habe? Weil geschrieben wurde, dass Visual C++ 2018.9 große Verbesserungen in der Template-Konformität bringt: Use the official range-v3 with MSVC 2017 version 15.9
Der Artikel spornt zum Ausprobieren an, aber leider ist 2017.9 noch gar nicht released. Und im kack Visual Studio Installer sieht man ja immer erst nach dem Update, welche Version man kriegt.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Aktualisierung von Visual C++ 2017.8.9 auf 2017.9.1 … zwar groß angekündigte Änderungen am Frontend, aber nicht an der Code Generation. Assembly ist exakt identisch.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
WTF … der Sprachstandard hat Auswirkungen auf die Code Generation.
Bedeutet: Übergebt /std:c++latest (geht über Projektoptionen → C/C++ → Language → C++ Language Standard) und euer Projekt sollte anders optimiert werden als zuvor.
Bei mir hat sich ein enormer Unterschied in einer Schleife gezeigt, die eine Zeile Pixel weiß färbt:
while(toPixel < toLineEnd) {
store(*toPixel++, white());
}
Mit dem C++14-Standard erzeugt Visual Studio an dieser Stelle einen rep stosd-Befehl (bestimmte Anzahl 32-Bit-Werte mit einem vorgegebenen Wert füllen); mit dem C++17-Standard erzeugt es eine Schleife. Der Unterschied ist schon recht beachtlich (um die 200 B, wenn man alle Stellen zusammenzählt, die betroffen sind).
Mein erster Verdacht ist Copy Elision – dass mein Pixel-struct durch den neuen Standard in irgendeine neue Kategorie gerutscht ist und deshalb anders optimiert wird. Es ist aber wirklich nur ein struct mit vier chars drin und ohne Methoden.
Ich wollte das genauer prüfen und isolieren, aber irgendwie bin ich total im Quantenschaum:
Außerdem muss ich nun alle meine Bug Reports mit /std:c++latest neu kompilieren – vielleicht sind die längst behoben …
Nachtrag: Was die Anzahl der Befehle betrifft: C++17 + reinterpret_cast < C++17 + memcpy = C++14 + memcpy < C++14 + reinterpret_cast
Kommt aber auf’s Programm an. Mein Viewer z. B. ist nun 60 B größer. Ffffffffuuuuu
Bedeutet: Übergebt /std:c++latest (geht über Projektoptionen → C/C++ → Language → C++ Language Standard) und euer Projekt sollte anders optimiert werden als zuvor.
Bei mir hat sich ein enormer Unterschied in einer Schleife gezeigt, die eine Zeile Pixel weiß färbt:
while(toPixel < toLineEnd) {
store(*toPixel++, white());
}
Mit dem C++14-Standard erzeugt Visual Studio an dieser Stelle einen rep stosd-Befehl (bestimmte Anzahl 32-Bit-Werte mit einem vorgegebenen Wert füllen); mit dem C++17-Standard erzeugt es eine Schleife. Der Unterschied ist schon recht beachtlich (um die 200 B, wenn man alle Stellen zusammenzählt, die betroffen sind).
Mein erster Verdacht ist Copy Elision – dass mein Pixel-struct durch den neuen Standard in irgendeine neue Kategorie gerutscht ist und deshalb anders optimiert wird. Es ist aber wirklich nur ein struct mit vier chars drin und ohne Methoden.
Ich wollte das genauer prüfen und isolieren, aber irgendwie bin ich total im Quantenschaum:
- store() kopiert ein unsigned int an eine Zieladresse des Typs struct Pixel via reinterpret_cast. Das ist undefined Behavior; eigentlich sollte ich memcpy() benutzen. Tue ich das, wird das Assembly schlechter – aber bei weitem nicht so schlecht wie mit dem 14er-Standard.
- Der rep stosd-Befehl erwartet eine Anzahl 32-Bit-Werte – keine Größe. Nach dem Subtrahieren der Zeiger teil Visual C++ also durch 4. Aber vorher addiert es 3 – rundet also auf. Das sollte niemals nie eine Rolle spielen, weil das Subtrahieren von Zeigern nur innerhalb des selben Arrays definiert ist, und dort immer eine glatte Größe ergibt. WTF. Das lässt befürchten, dass Zeiger-Subtraktionen in Visual C++ generell suboptimalen Code erzeugen (deckt sich mit früheren Beobachtungen).
- Wenn ich rep stosd via Intrinsic explizit erzeuge, wird der Code anders, aber nicht besser.
- Viele andere Funktionen sind ebenfalls betroffen und ordnen ihre Register unterschiedlich zu; dort sind die Änderungen aber weniger deutlich.
- x86 und x64 verhalten sich genau gegensätzlich: Wird der Code auf x64 200 B kompakter, wird er auf x86 300 B größer.
Außerdem muss ich nun alle meine Bug Reports mit /std:c++latest neu kompilieren – vielleicht sind die längst behoben …
Nachtrag: Was die Anzahl der Befehle betrifft: C++17 + reinterpret_cast < C++17 + memcpy = C++14 + memcpy < C++14 + reinterpret_cast
Kommt aber auf’s Programm an. Mein Viewer z. B. ist nun 60 B größer. Ffffffffuuuuu
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Meine ständigen Abstürze mit IntelliSense sind lt. Microsoft auf Sonderzeichen nahe Zeilenenden zurückzuführen. Bis auf Weiteres solltet ihr (auch in Kommentaren) keine Sonderzeichen direkt vor Zeilenenden platzieren.
- Schrompf
- Moderator
- Beiträge: 5047
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
WTF?
Immerhin: meinen "this-call aus Lambda verkackt const-Overload" haben sie angeblich gefixt, schrieb mir neulich ein MS-Bot per Mail.
Immerhin: meinen "this-call aus Lambda verkackt const-Overload" haben sie angeblich gefixt, schrieb mir neulich ein MS-Bot per Mail.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Die Frage ist dann nur, wie lange es dauert, bis der Patch ausgerollt wird … ich fürchte, dass VS 2017 nun tot ist und nur noch 2019 die coolen Updates kriegen wird.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Mein Ticket zu den Änderungen am Inlining wurde abgelehnt, weil die Korrektur zu komplex wäre und nur wenige Fälle verbessern würde.
Aber der zuständige Entwickler hat mir einen geheimen Schalter offenbart: /d2inlSn steuert das Inlining-Threshold, wobei n standardmäßig 9 (für 32-Bit-Programme) oder 32 (für 64-Bit-Programme) ist.
Hier also ein Diagramm der Größe meines 64-Bit-STL-Viewers – kompiliert auf Größe – in Abhängigkeit zur Option:
Das Optimum liegt nicht etwa bei 32 sondern bei 11. Der steile Anstieg zwischen 15 und 18 ist exakt mein Bug Report. Der Unterschied zwischen 11 und dem Standard (32) ist 1 %; der Unterschied zwischen 11 und 18 sind 1,2 %.
Beachtet, dass das direkt die Ausführungsgeschwindigkeit beeinträchtigen kann – schließlich schalten wir hier Inlining kleiner Funktionen selektiv ab!
Aber der zuständige Entwickler hat mir einen geheimen Schalter offenbart: /d2inlSn steuert das Inlining-Threshold, wobei n standardmäßig 9 (für 32-Bit-Programme) oder 32 (für 64-Bit-Programme) ist.
Hier also ein Diagramm der Größe meines 64-Bit-STL-Viewers – kompiliert auf Größe – in Abhängigkeit zur Option:
Das Optimum liegt nicht etwa bei 32 sondern bei 11. Der steile Anstieg zwischen 15 und 18 ist exakt mein Bug Report. Der Unterschied zwischen 11 und dem Standard (32) ist 1 %; der Unterschied zwischen 11 und 18 sind 1,2 %.
Beachtet, dass das direkt die Ausführungsgeschwindigkeit beeinträchtigen kann – schließlich schalten wir hier Inlining kleiner Funktionen selektiv ab!
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Wenn man was an String Literals ändert, tun sich jedes Mal Abgründe auf.
Ich habe jetzt angefragt, dass sie bitte das Alignment von String Literals entfernen sollen. Dieser Quelltext:… erzeugt in den Read-Only-Daten der EXE einen Eintrag mit den 16 Buchstaben error: no memory und einer abschließenden Null. Und dann – das ist das Problem – noch sieben Null-Bytes um den String auf ein Vielfaches von acht Bytes zu padden (auf x64; auf x86 drei Null-Bytes für ein Vielfaches von vier Bytes).
Warum? Man kann nur mutmaßen. Bei GCC war das Argument, dass die meisten String Literals direkt an memcpy() oder strcpy() oder Verwandte wandern, und die nutzen in ihren inneren Schleifen oft SSE, und das kann mit runden Adressen schneller kopieren.
Aber Visual C++ tut das auch, wenn ich auf Größe statt auf Geschwindigkeit optimieren möchte. Und das ist definitiv falsch.
Es kommt besser: alignas() funktioniert als Workaround! Wenn man die Funktion also ändert zu… dann werden keine zusätzlichen Null-Bytes angehängt und alles landet dicht gepackt in der EXE, wie es soll. Aber ihr könnt euch vorstellen, was für ein Aufwand es wäre, alle Codebases so umzustellen und WTF wer würde sich den Quelltext überhaupt so vermiesen wollen.
Deshalb der Bug Report an MS: https://developercommunity.visualstudio ... trict.html
Noch besser ist übrigens, dass ihr die Deklaration oben auf keinen Fall berühren dürft. static constexpr char x[] ginge noch. char const x[] würde schon um Größenordnungen schlechteren Code erzeugen, weil Visual C++ da auch einen Bug hat (warum habe ich das eigentlich noch nicht gemeldet?!). constexpr char x[] wäre ebenfalls katastrophal. Wenn ihr’s nicht glaubt, probiert’s mal aus. Ich kann nicht so viel tippen wie ich kotzen will.
Ich habe jetzt angefragt, dass sie bitte das Alignment von String Literals entfernen sollen. Dieser Quelltext:
Code: Alles auswählen
char const * getError() {
return "error: no memory";
}
Warum? Man kann nur mutmaßen. Bei GCC war das Argument, dass die meisten String Literals direkt an memcpy() oder strcpy() oder Verwandte wandern, und die nutzen in ihren inneren Schleifen oft SSE, und das kann mit runden Adressen schneller kopieren.
Aber Visual C++ tut das auch, wenn ich auf Größe statt auf Geschwindigkeit optimieren möchte. Und das ist definitiv falsch.
Es kommt besser: alignas() funktioniert als Workaround! Wenn man die Funktion also ändert zu
Code: Alles auswählen
char const * getError() {
alignas(char) static char const x[] = "error: out of memory";
return x;
}
Deshalb der Bug Report an MS: https://developercommunity.visualstudio ... trict.html
Noch besser ist übrigens, dass ihr die Deklaration oben auf keinen Fall berühren dürft. static constexpr char x[] ginge noch. char const x[] würde schon um Größenordnungen schlechteren Code erzeugen, weil Visual C++ da auch einen Bug hat (warum habe ich das eigentlich noch nicht gemeldet?!). constexpr char x[] wäre ebenfalls katastrophal. Wenn ihr’s nicht glaubt, probiert’s mal aus. Ich kann nicht so viel tippen wie ich kotzen will.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Neues Backend für SSE-Optimierungen: https://devblogs.microsoft.com/cppblog/ ... sion-16-2/
In großen Teilen kann ich bestätigen, was der Artikel sagt. Praktisch fallen die Gewinne meist geringer aus als in den Fällen, die sie da präsentieren; aber die größten Facepalm-Momente sind vorbei.
(Mein STL-Viewer: ~10 Befehle weniger (<0,1%!); mein S.T.A.L.K.E.R.-Build: 8 KiB mehr Code vor allem durch geänderte Inline-Heuristik.)
AUßER – und das erwähnen sie nicht – bei Integer-Anweisungen. Integer-Intrinsics erzeugen noch immer Anweisungen auf 2005er Niveau. Offenbar haben sie nur auf float-Vektorisierung abgezielt.
In großen Teilen kann ich bestätigen, was der Artikel sagt. Praktisch fallen die Gewinne meist geringer aus als in den Fällen, die sie da präsentieren; aber die größten Facepalm-Momente sind vorbei.
(Mein STL-Viewer: ~10 Befehle weniger (<0,1%!); mein S.T.A.L.K.E.R.-Build: 8 KiB mehr Code vor allem durch geänderte Inline-Heuristik.)
AUßER – und das erwähnen sie nicht – bei Integer-Anweisungen. Integer-Intrinsics erzeugen noch immer Anweisungen auf 2005er Niveau. Offenbar haben sie nur auf float-Vektorisierung abgezielt.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
#pragma message funktioniert seit geraumer Zeit unter Umständen nicht mehr: https://developercommunity.visualstudio ... n-the.html
Ich dachte erst, meine Makros wären nicht standardkonform, deshalb hatte ich es nicht sofort gemeldet …
Ich dachte erst, meine Makros wären nicht standardkonform, deshalb hatte ich es nicht sofort gemeldet …
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Nur als Beweis, dass ich nicht paranoid oder bekloppt bin:
Sie haben im Sommer still und heimlich das ganze Inlining ausgetauscht, um es aggressiver zu machen.https://devblogs.microsoft.com/cppblog/msvc-backend-updates-in-visual-studio-2019-versions-16-3-and-16-4/ hat geschrieben:Visual Studio 2019 version 16.4
…
- Enabling of the enhanced inliner introduced in 16.3 by default, without the use of /Ob3.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Falls sich jemand wundert, warum er seit neuestem komische Abschnitte namens _RDATA in seinem Kompilat hat, bitte upvoten: https://developercommunity.visualstudio ... ction.html
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Ein neuer Monat, ein neuer Visual C++-Bug: https://developercommunity.visualstudio ... me0-a.html
Ich habe immer empfohlen, nicht erreichbare Pfade mit __assume(0) zu markieren, damit der Optimizer sie wegoptimieren kann. Speziell betrifft das bei mir einen Haufen Aufräum-Code am Ende der main(), der nie erreicht wird, weil ein Klick aufs X des Hauptfensters den Prozess via ExitProcess() abräumt. Aber auch in switch lässt sich was sparen (sogar deutlich messbar!), wenn default nicht erreichbar ist.
Aber nun habe ich gemerkt, dass das Inlining dann nicht mehr funktioniert. Zumindest in Funktionen, in denen ein __assume(0) auftaucht oder in denen eine Funktion mit [[noreturn]]-Attribut aufgerufen wird. Meh.
Ich habe immer empfohlen, nicht erreichbare Pfade mit __assume(0) zu markieren, damit der Optimizer sie wegoptimieren kann. Speziell betrifft das bei mir einen Haufen Aufräum-Code am Ende der main(), der nie erreicht wird, weil ein Klick aufs X des Hauptfensters den Prozess via ExitProcess() abräumt. Aber auch in switch lässt sich was sparen (sogar deutlich messbar!), wenn default nicht erreichbar ist.
Aber nun habe ich gemerkt, dass das Inlining dann nicht mehr funktioniert. Zumindest in Funktionen, in denen ein __assume(0) auftaucht oder in denen eine Funktion mit [[noreturn]]-Attribut aufgerufen wird. Meh.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Ich habe Antwort bekommen, und wann immer man denkt, es könnte nicht schlimmer kommen …
Also: *Jeder* Code-Pfad, der in einem Abbruch endet – egal, ob in throw oder exit() oder __assume(0) oder [[noreturn]] – wird vom Inlining ausgeschlossen. Das dient dazu, nicht so viel Zeit auf der Optimierung von „kaltem“ Code wie Fehlerbehandlung zu verbrauchen.
Das ist eine schlechte Nachricht für jeden, der ein kleines Tool schreibt, den größten Brocken in die main() packt, und dann via exit() beendet statt via return. Godbolt bestätigt: https://godbolt.org/z/KRWs8d
Immerhin ist nur der direkte Pfad betroffen, und andere Optimierungen finden noch eingeschränkt statt. Aber WTF. Die Schleife ist eindeutig heiß; eindeutiger geht’s nicht!
Also: *Jeder* Code-Pfad, der in einem Abbruch endet – egal, ob in throw oder exit() oder __assume(0) oder [[noreturn]] – wird vom Inlining ausgeschlossen. Das dient dazu, nicht so viel Zeit auf der Optimierung von „kaltem“ Code wie Fehlerbehandlung zu verbrauchen.
Das ist eine schlechte Nachricht für jeden, der ein kleines Tool schreibt, den größten Brocken in die main() packt, und dann via exit() beendet statt via return. Godbolt bestätigt: https://godbolt.org/z/KRWs8d
Immerhin ist nur der direkte Pfad betroffen, und andere Optimierungen finden noch eingeschränkt statt. Aber WTF. Die Schleife ist eindeutig heiß; eindeutiger geht’s nicht!
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Zwei Tickets für die Optimierung von Integern. Bittet votet sie hoch, falls euch minimal schnelleres sin() cos() tan() exp() log() pow() dtoa() wichtig ist ;)
Zum einen prüft Visual Studio nicht optimal, ob ein unsigned int größer oder gleich 0x80000000 ist. Das muss man von Hand ausschreiben als
if(static_cast<int>(ui) < 0) // entspricht if(ui >= 0x80000000), ist aber schneller und kompakter
Clang und GCC führen diese Optimierung durch, aber VC verpatzt sie. Ticket
Zum anderen nutzt VC nicht immer die Bit-Test-and-Reset-Anweisung, wenn das höchste Bit einer Zahl genullt werden soll, sondern fällt auf das viel größere (und minimal langsamere) AND zurück. Ticket
Dieser Fall ist spannender – Visual C++ tut das nur bei 32-Bit-Integern, aber nicht bei 64-Bit-Integern (wo dann doppelt so viele Befehle mit zehn Mal so vielen Bytes generiert werden). GCC tut das nur bei 64-Bit-Integern, aber nicht bei 32-Bit-Integern. Clang tut es überhaupt nicht. Bei 8- und 16-Bit-Integern kann ich mir vorstellen, dass es sich um einen Tradeoff handelt; bei 32-Bit-Integern überlege ich glatt, ob ich nicht auch Bugs bei Clang und GCC melden soll.
Nachtrag: Hier erklären die LLVM-Leute ihre Entscheidung: https://reviews.llvm.org/D48606#1144088
Das AND hat auf modernen Intel-CPUs höheren Durchsatz als das BTR. Bei 64-Bit-Ints und Optimierung auf Größe nutzen sie BTR; in den anderen Fällen AND. Ist konsistent mit meinen Beobachtungen.
Zum einen prüft Visual Studio nicht optimal, ob ein unsigned int größer oder gleich 0x80000000 ist. Das muss man von Hand ausschreiben als
if(static_cast<int>(ui) < 0) // entspricht if(ui >= 0x80000000), ist aber schneller und kompakter
Clang und GCC führen diese Optimierung durch, aber VC verpatzt sie. Ticket
Zum anderen nutzt VC nicht immer die Bit-Test-and-Reset-Anweisung, wenn das höchste Bit einer Zahl genullt werden soll, sondern fällt auf das viel größere (und minimal langsamere) AND zurück. Ticket
Dieser Fall ist spannender – Visual C++ tut das nur bei 32-Bit-Integern, aber nicht bei 64-Bit-Integern (wo dann doppelt so viele Befehle mit zehn Mal so vielen Bytes generiert werden). GCC tut das nur bei 64-Bit-Integern, aber nicht bei 32-Bit-Integern. Clang tut es überhaupt nicht. Bei 8- und 16-Bit-Integern kann ich mir vorstellen, dass es sich um einen Tradeoff handelt; bei 32-Bit-Integern überlege ich glatt, ob ich nicht auch Bugs bei Clang und GCC melden soll.
Nachtrag: Hier erklären die LLVM-Leute ihre Entscheidung: https://reviews.llvm.org/D48606#1144088
Das AND hat auf modernen Intel-CPUs höheren Durchsatz als das BTR. Bei 64-Bit-Ints und Optimierung auf Größe nutzen sie BTR; in den anderen Fällen AND. Ist konsistent mit meinen Beobachtungen.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
MSVC Backend Updates in Visual Studio 2019 version 16.9 Preview 3:
Hmmm. Die Compiler-Version hat sich von 19.28.29617 zu 19.28.29812 geändert, aber ich sehe absolut keinen Unterschied in der Code Generation. In keinem meiner Programme. Schade.
- Swap order of loop fusion and vectorization
Improved loop optimizer to apply loop fusion in many more cases.- Remove unnecessary memory loads using the reg-mem variants of commutative x86 instructions
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Oh fucking fuck, es war ja klar, dass ich mich mit meinem Lehrbuchwissen in die Scheiße setze.Krishty hat geschrieben: ↑29.08.2018, 10:22Wenn ihr z.B. float zu int konvertiert, ist reinterpret_cast undefiniertes Verhalten (obwohl Visual C++ es frisst) und union ebenfalls (obwohl GCC/Clang es fressen). Um standardkonform zu sein, solltet ihr das via memcpy() machen (ist übrigens auch in allen aktuellen Compilern die am besten optimierte Methode).
Nein, für float-zu-int & Co. auf Visual Studio ist memcpy() leider nicht die beste Methode. Für alle Bit-Casts, die nur mit Integer-Typen zu tun haben, ja. Sobald float und double im Spiel sind, nein.
Speziell:
Code: Alles auswählen
static double asdouble(uint64_t const bits) {
// Outperforms memcpy() on Visual C++ 2019
return _mm_cvtsd_f64(_mm_castsi128_pd(_mm_cvtsi64_si128(bits)));
}
static uint64_t asuint64(double const d) {
// Outperforms memcpy() on Visual C++ 2019
return _mm_cvtsi128_si64(_mm_castpd_si128(_mm_set_sd(d)));
}
static float asfloat(uint32_t const bits) {
// Outperforms memcpy() on Visual C++ 2019
return _mm_cvtss_f32(_mm_castsi128_ps(_mm_cvtsi32_si128(bits)));
}
static uint32_t asuint(float const f) {
// Outperforms memcpy() on Visual C++ 2019
return _mm_cvtsi128_si32(_mm_castps_si128(_mm_set_ss(f)));
}
Nachtrag: Bug-Report ist raus: https://developercommunity.visualstudio ... bitwi.html
- Schrompf
- Moderator
- Beiträge: 5047
- Registriert: 25.02.2009, 23:44
- Benutzertext: Lernt nur selten dazu
- Echter Name: Thomas
- Wohnort: Dresden
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Du bist ein Held. Nicht unbedingt der Held der Massen, aber mein ganz persönlicher.
Früher mal Dreamworlds. Früher mal Open Asset Import Library. Heutzutage nur noch so rumwursteln.
- Krishty
- Establishment
- Beiträge: 8316
- Registriert: 26.02.2009, 11:18
- Benutzertext: state is the enemy
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Danke! Ich bin sicher auch der Antiheld in Redmond :)
Hier ist der komplette Workaround:Mittlerweile habe ich über 5000 Zeilen von so Funktionen, die eigentlich zu einem einzigen Befehl kompilieren sollten, aber 10 Zeilen Compiler-spezifischen Code brauchen, das auch zu tun. Wenn ich Assembler programmieren würde, wäre der Block literally 16 Buchstaben kurz …
Nun habe ich quer durch meine Projekte einige Hundert Bytes und einige Takte gespart, aber … die Code Generation ist immernoch nicht so gut wie mit memcpy() als Makro. Also, die Stellen mit float und double sind viel besser – viele andere aber nicht.
Also weiter Testfälle erstellen und reduzieren …
Hier ist der komplette Workaround:
Code: Alles auswählen
// Reinterprets bits as a floating-point number.
[[nodiscard]] float floatFromBits(UInt4B const bits) {
# if !__clang__ && _MSC_VER
// • x86 has the MOVD instruction for this
// • Visual C++ emits MOVD only via this intrinsic chain (see Developer Community ticket 1332369)
return _mm_cvtss_f32(_mm_castsi128_ps(_mm_cvtsi32_si128(bits)));
# else
// Clang/GCC optimize this well (e.g. to a single MOVD on x86):
float result;
__builtin_memcpy(&result, &bits, sizeof bits);
return result;
# endif
}
[[nodiscard]] double doubleFromBits(UInt8B const bits) {
# if !__clang__ && _MSC_VER
# if _M_AMD64
// • x86-64 has the MOVQ instruction for this
// • Visual C++ emits MOVQ only via this intrinsic chain (see Developer Community ticket 1332369)
return _mm_cvtsd_f64(_mm_castsi128_pd(_mm_cvtsi64x_si128(bits)));
# else
// x86-32 has no dedicated instruction for this; let the compiler figure it out:
double result;
memcpy(&result, &bits, sizeof bits);
return result;
# endif
# else
// Clang/GCC optimize this well (e.g. to a single MOVQ on x86-64):
double result;
__builtin_memcpy(&result, &bits, sizeof bits);
return result;
# endif
}
// Reinterprets the bits of a floating-point number an integer.
[[nodiscard]] UInt4B bitsOf(float const f) {
# if !__clang__ && _MSC_VER
// • x86 has the MOVD instruction for this
// • Visual C++ emits MOVD only via this intrinsic chain (see Developer Community ticket 1332369)
return _mm_cvtsi128_si32(_mm_castps_si128(_mm_set_ss(f)));
# else
// Clang/GCC optimize this well (e.g. to a single MOVD on x86):
UInt4B result;
__builtin_memcpy(&result, &f, sizeof f);
return result;
# endif
}
[[nodiscard]] UInt8B bitsOf(double const d) {
# if !__clang__ && _MSC_VER
# if _M_AMD64
// • x86-64 has the MOVQ instruction for this
// • Visual C++ emits MOVQ only via this intrinsic chain (see Developer Community ticket 1332369)
return _mm_cvtsi128_si64x(_mm_castpd_si128(_mm_set_sd(d)));
# else
// x86-32 has no dedicated instruction for this; let the compiler figure it out:
UInt8B result;
memcpy(&result, &d, sizeof d);
return result;
# endif
# else
// Clang/GCC optimize this well (e.g. to a single MOVQ on x86-64):
UInt8B result;
__builtin_memcpy(&result, &d, sizeof d);
return result;
# endif
}
Nun habe ich quer durch meine Projekte einige Hundert Bytes und einige Takte gespart, aber … die Code Generation ist immernoch nicht so gut wie mit memcpy() als Makro. Also, die Stellen mit float und double sind viel besser – viele andere aber nicht.
Also weiter Testfälle erstellen und reduzieren …
-
- Establishment
- Beiträge: 308
- Registriert: 25.08.2019, 05:00
- Alter Benutzername: gdsWizard
- Kontaktdaten:
Re: Sammelthread zu Visual C++’ Compiler
Dem kann ich mich nur anschließen. Unglaublich womit Krishty sich alles beschäftigt und auch versteht.Schrompf hat geschrieben:Du bist ein Held. Nicht unbedingt der Held der Massen, aber mein ganz persönlicher.
Man muß eben immer alles von verschiedenen Standpunkten beleuchten...Krishty hat geschrieben:Danke! Ich bin sicher auch der Antiheld in Redmond :)
Hat den StormWizard 1.0 und 2.0 verbrochen. https://mirrorcad.com