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: 1734
Registriert: 06.03.2004, 18:10
Echter Name: Michael Kenzel
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von dot »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

yeah
win7.png
(Quelle)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 4831
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Schrompf »

WTF?

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.
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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) 7166 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: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

#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 …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

Krishty hat geschrieben: 08.08.2019, 20:56mein S.T.A.L.K.E.R.-Build: 8 KiB mehr Code vor allem durch geänderte Inline-Heuristik
Nur als Beweis, dass ich nicht paranoid oder bekloppt bin:
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.
Sie haben im Sommer still und heimlich das ganze Inlining ausgetauscht, um es aggressiver zu machen.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

Falls sich jemand wundert, warum er seit neuestem komische Abschnitte namens _RDATA in seinem Kompilat hat, bitte upvoten: https://developercommunity.visualstudio ... ction.html
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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!
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

MSVC Backend Updates in Visual Studio 2019 version 16.9 Preview 3:
  • 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
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.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

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).
Oh fucking fuck, es war ja klar, dass ich mich mit meinem Lehrbuchwissen in die Scheiße setze.

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)));
}
Ihr wollt wissen, wie viel das ausmacht? Nun, mein powf() hat sich von 50 auf 40 Takte verkürzt und ist 30 Bytes kleiner geworden, ausschließlich deswegen.

Nachtrag: Bug-Report ist raus: https://developercommunity.visualstudio ... bitwi.html
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Schrompf
Moderator
Beiträge: 4831
Registriert: 25.02.2009, 23:44
Benutzertext: Lernt nur selten dazu
Echter Name: Thomas Ziegenhagen
Wohnort: Dresden
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Schrompf »

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.
Benutzeravatar
Krishty
Establishment
Beiträge: 8227
Registriert: 26.02.2009, 11:18
Benutzertext: state is the enemy
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Krishty »

Danke! Ich bin sicher auch der Antiheld in Redmond :)

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
}
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 …
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Mirror
Establishment
Beiträge: 245
Registriert: 25.08.2019, 05:00
Alter Benutzername: gdsWizard
Kontaktdaten:

Re: Sammelthread zu Visual C++’ Compiler

Beitrag von Mirror »

Schrompf hat geschrieben:Du bist ein Held. Nicht unbedingt der Held der Massen, aber mein ganz persönlicher.
Dem kann ich mich nur anschließen. Unglaublich womit Krishty sich alles beschäftigt und auch versteht.
Krishty hat geschrieben:Danke! Ich bin sicher auch der Antiheld in Redmond :)
Man muß eben immer alles von verschiedenen Standpunkten beleuchten...
Hat den StormWizard 1.0 und 2.0 verbrochen. http://www.mirrorcad.com
Antworten