Sammelthread zu Visual C++’ Compiler

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.
Benutzeravatar
dot
Establishment
Beiträge: 1657
Registriert: 06.03.2004, 19:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von dot » 07.06.2018, 10:59

Schrompf hat geschrieben:bringst Du dort regelmäßig Kuchen vorbei? :-)
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:

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 07.06.2018, 21:47

Ich bin ein kleines Bisschen irritiert :D Ja los, schaufel alles rein …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 09.06.2018, 22:28

yeah
win7.png
(Quelle)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 14.07.2018, 12:11

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 17.08.2018, 17:32

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 17.08.2018, 20:41

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:

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)
53 Befehle, und dazu recht kompakte. Jetzt die x64-Version mit den selben Einstellungen, ebenfalls für Größe(!) optimiert:

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) 
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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 21.08.2018, 19:07

Bug Report für die absurde Größe auf x64 ist raus: https://developercommunity.visualstudio ... r-siz.html
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 21.08.2018, 22:44

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 29.08.2018, 10:22

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 …
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.
Äh, wartet. Euer Compiler ist auf .NET gebaut?!
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 23.09.2018, 13:40

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 …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 07.11.2018, 19:16

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 18.11.2018, 23:53

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 15.12.2018, 21:07

WTF … der Sprachstandard hat Auswirkungen auf die Code Generation.

Bedeutet: Übergebt /std:c++latest (geht über Projektoptionen → C/C++LanguageC++ 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.
Kurz: ich bin da mitten in einem chaotischen System.

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
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 16.01.2019, 22:47

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Schrompf
Moderator
Beiträge: 3805
Registriert: 26.02.2009, 00:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Schrompf » 17.01.2019, 14:27

WTF?

Immerhin: meinen "this-call aus Lambda verkackt const-Overload" haben sie angeblich gefixt, schrieb mir neulich ein MS-Bot per Mail.
Häuptling von Dreamworlds. Baut an was Neuem. Hilft nebenbei nur höchst selten an der Open Asset Import Library mit.

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 17.01.2019, 21:59

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 13.02.2019, 15:54

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:
d2inlS.png
d2inlS.png (8.86 KiB) 670 mal betrachtet

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!
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Benutzeravatar
Krishty
Establishment
Beiträge: 6813
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty » 08.03.2019, 16:01

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:

Code: Alles auswählen

char const * getError() {
    return "error: no memory";
}
… 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

Code: Alles auswählen

char const * getError() {
    alignas(char) static char const x[] = "error: out of memory";
    return x;
}
… 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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne

Antworten