Sammelthread zu Visual C++’ Compiler

Programmiersprachen, APIs, Bibliotheken, Open Source Engines, Debugging, Quellcode Fehler und alles was mit praktischer Programmierung zu tun hat.

Sammelthread zu Visual C++’ Compiler

Beitragvon Krishty » 07.03.2018, 15:34

… bevor ich den Jammer-Thread vollends flute, sammle ich meine Beschwerden und Entdeckungen hier.

Also … Update von Visual Studio 15.5.5 auf 15.6. Einige Programme werden größer.

Ursache scheinen zwei Änderungen in der CRT zu sein:

  1. Eine der Standardfunktionen nutzt nun Exception Handling (ich befürchte, eine von sin() cos() tan() exp() pow()). Also landet nun Exception Handling in meiner EXE.
    Ich sehe folgende neue Symbole in meinen Modulen:
    • 192 B: __acrt_exception_action_table (exception_filter.obj)
    • 132 B: __DestructExceptionObject (ehhelpers.obj)
    • 64 B: _CallSettingFrame (handlers.obj)
    • 24 B: __DestructExceptionObject$filt$0 (ehhelpers.obj)
  2. Die CRT bringt eine Funktion mit, um die Dekoration von Funktionsnamen zu entfernen (UnDecorator). Diese Funktion wurde umgeschrieben, so dass sie nun strncmp() nutzt (geraten – könnte auch das Exception Handling sein). Gelöscht:
    • 8 B: UnDecorator::outputString (undname.obj)
    • 4 B: UnDecorator::maxStringLength (undname.obj)
    Neu hinzugefügt:
    • 125 B: strncmp (strncmp.obj)
    • 4 B: UnDecorator::m_CHPENameOffset (undname.obj)
    • 4 B: UnDecorator::m_recursionLevel (undname.obj)
  3. Irgendwo wird eine Lookup-Tabelle __newctype benutzt (768 B).
    Dummerweise finde ich die betreffende Datei ctype.c nicht in dem CRT-Quelltext, darum weiß ich nicht mehr drüber.

… das kostet um die 500 B zusätzlichen Code und rund 1000 B zusätzliche Daten.

Das mit den Exceptions wäre vermeidbar gewesen, da bin ich mir sicher.

Weiter mit der Code Generation:

  1. optimize for speed: return ~0; erzeugt nun statt OR EAX, -1 (drei Bytes) MOV EAX, -1 (fünf Bytes).

  2. optimize for speed: Sprünge erzwingen nun häufiger glatte Adressen (viel mehr Padding).

    15.5 (Schleifenanfang sieben Bytes vom Funktionsanfang):
      49 FF C1                    INC R9

    15.6 (selbe Situation):
      66 0F 1F 84 00 00 00 00 00  NOP WORD PTR[RAX+RAX]
      49 FF C1                    INC R9


  3. optimize for size: Mehrfache _mm_setzero_ps() werden nun zusammengefasst. Wenn in 15.5 zwei Funktionen einen Vektor via _mm_setzero_ps() genullt haben, landeten sehr oft auch zwei XORPS im Code. Ab 15.6 werden sie öfter zusammengefasst.

    Das ist schwer zu entscheiden – meist möchte man tatsächlich doppelt nullen, um die Parallelität zu erhöhen. Wenn aber z.B. die Abhängigkeitsketten gleich lang sind, ist es sinnlos, zwei Register zu nullen. Ich vermute, dass sie was in diese Richtung verbessert haben.

    Die Optimierung greift durchaus an 10, 20 Stellen in meinen Programmen und spart dort jeweils ein paar Bytes. Der Gewinn kommt eher nicht durch weniger XORPS-Befehle, sondern durch reduzierten Registerdruck (weniger Spilling auf den Stack).

    Gut möglich, dass diese Optimierung auch bei Geschwindigkeitsoptimierung greift – dort wird sie aber wohl durch die anderen Änderungen verdrängt.

    Ich messe hier zum ersten Mal, dass meine Programme durch ein Release minimal kleiner geworden sind. Schön. Aber eben nur, sofern man nicht die statische CRT linkt und von dem Kilobyte neuem Bloat getroffen wurde :(
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6349
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon Krishty » 11.03.2018, 03:39

Ich habe wieder einen Bug gemeldet: https://developercommunity.visualstudio ... s-mix.html

Visual C++ verwechselt Funktionsparameter in Code, der viel SSE einsetzt. Ich hatte den Bug Report Monate vor mir hergeschoben weil ich wusste, dass es PITA sein würde, ihn zu reproduzieren – und tatsächlich hat’s mich vier Stunden gekostet. Falls sie ihn anerkennen, wird es wohl um die sechs Monate dauern, bis ein Fix ausgerollt ist. Sonst komme ich persönlich vorbei und erinnere sie dran.

Fun fact: Erfordert das Schreiben durch einen Zeiger innerhalb eines if in einer __vectorcall-Funktion mit einer bestimmten Anzahl Parametern und einer bestimmten Anzahl lokaler Zwischenergebnisse. Dann ist aber eben nicht der geschriebene Wert kaputt, sondern ein ganz anderer.

Wie man darauf kommt? Indem man Vertex Shader Constants aktualisiert, und irgendwie die Farbe der Objekte nicht so ganz richtig ist.

Repro:
Code: Ansicht erweitern :: Alles auswählen
// compile with /LTCG /O1 (favor size, not speed)


#include <emmintrin.h>
#include <stdio.h>



// REQUIRED: must use struct -- bug does not show using __m128 directly
// (may be related to 140160 "_vectorcall calling convention via struct accesses wrong xmm register")
struct floatx4 {
        __m128 f;
};
struct floatx12 {
        floatx4 c0, c1, c2;
};

floatx4 __vectorcall zero() {
        return { _mm_setzero_ps() };
}
floatx4 __vectorcall operator +(floatx4 a, floatx4 b) {
        return { _mm_add_ps(a.f, b.f) };
}


floatx12 __vectorcall someMath(
        floatx4   v,
        floatx12  m
) {
        return {
                m.c0,
                m.c1,
                v + m.c2 // REQUIRED
        };
}



struct SIDE_EFFECT {
        floatx4  global_0;
        floatx4  global_1;
};

SIDE_EFFECT global_side_effect = { };


// REQUIRED: must be __vectorcall
// REQUIRED: inlining hides the bug
void __declspec(noinline) __vectorcall inner(
        floatx4   param_0_should_be_zero,
        floatx12  param_1,
        floatx12  param_2_unused_but_required,
        floatx4   param_3
) {
        // this should print 0 0 0 0 but instead it prints param_3
        printf("should be zero: %f %f %f %f\n", param_0_should_be_zero.f.m128_f32[0], param_0_should_be_zero.f.m128_f32[1], param_0_should_be_zero.f.m128_f32[2], param_0_should_be_zero.f.m128_f32[3]);
        // REQUIRED: bug does not show if printing param_3

        // REQUIRED: must use a pointer; accessing the structure directly hides the bug
        auto const side_effect_ptr = &global_side_effect;
        // REQUIRED: must branch; removing the if hides the bug
        if(nullptr != side_effect_ptr) {

                // REQUIRED: writing param_3 + param_1.c2 hides the bug although it should be equivalent
                // ARBITRARY: can also use param_2 instead
                // ARBITRARY: can flip order of operands
                side_effect_ptr->global_0 = someMath(param_3, param_1).c2;
                side_effect_ptr->global_1 = param_3;

        }
}

void outer( // inlining and __vectorcall here have no effect on the bug
        floatx4   param_0_is_zero,
        floatx12  param_1
) {
        inner(
                param_0_is_zero,
                param_1,
                param_1,
                param_1.c0 + param_1.c1 // REQUIRED: arbitrary operands, but *some* computation must take place
        );
}

int main() {

        // Arbitrary vectors, but NOT zero!
        floatx12 arbitrary = {
                { { 1, 2, 3, 4 } },
                { { 1, 2, 3, 4 } },
                { { 1, 2, 3, 4 } }
        };

        outer(zero(), arbitrary);

        getchar();
}
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6349
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon Schrompf » 11.03.2018, 13:07

Wow. Sehr cool, dass Du Dir die Zeit genommen hast. Hoffentlich nehmen die sich auch die Zeit.
Häuptling von Dreamworlds. Baut an was Neuem. Hilft nebenbei nur höchst selten an der Open Asset Import Library mit.
Benutzeravatar
Schrompf
Thomas Ziegenhagen
Moderator
 
Beiträge: 3684
Registriert: 26.02.2009, 00:44
Wohnort: Dresden
Benutzertext: Lernt nur selten dazu

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon Krishty » 12.03.2018, 23:35

Danke! Im Fall des nächsten Bugs kam ich kaum drumherum: Unsere Software auf der Arbeit funktionierte perfekt, als sie mit Visual Studio 2015 kompiliert wurde. Aber mit 2017 produziert sie nur Müll, und damit war das Update für uns erstmal erledigt.

Ich habe heute endlich binär die CPP-Dateien auf die Ursache durchsucht, indem ich immer für Gruppen von CPPs die Optimierung abgeschaltet habe. Als ich es auf eine CPP eingegrenzt hatte, bin ich die Funktionen mit #pragma optimize("", off) durchgegangen.

Ergebnis: https://developercommunity.visualstudio ... n-val.html

Dieser Bug ist deutlich stabiler als der letzte. Ich denke nicht, dass sie verwandt sind, weil andere Vorbedingungen gelten (erfordert kein struct).

Wie immer: Upvote, falls ihr mir helfen wollt.

Übrigens: Damit ist ein Drittel der Code Generation Bugs mit SSE (sechs an der Zahl) von mir gemeldet worden. Das lässt zwei Folgerungen zu:
  • Niemand außer mir setzt __vectorcall ein.
  • Entweder nutzt niemand DirectXMath, oder es stellt niedrigere Anforderungen an den Optimizer.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6349
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon Krishty » 21.03.2018, 17:07

Oh Gott, Loads & Stores zusammenfassen.

Ich möchte drei benachbarte Bytes r, g, b in ein int laden. Ganz alltäglich. Der Einfachheit halber pfeifen wir auf Endianness.

Naiv:

  int bgr = r | (g << 8) | (b << 16);

Katastrophal naive Befehlsfolge:

  movzx  eax,byte ptr [rdx+1]
  movzx  ecx,byte ptr [rdx+2]
  shl    ecx,8
  or     ecx,eax
  movzx  eax,byte ptr [rdx]
  shl    ecx,8
  or     ecx,eax


… also Schuss in den Ofen, wie immer. Erzwingen wir einen 16-b-Load:

  int bgr = *reinterpret_cast<short *>(&r) | (b << 16);

Perfekte Befehlsfolge:

  movzx  eax,word ptr [rdx]
  movzx  ecx,byte ptr [rdx+2]
  shl    ecx,10h
  or     ecx,eax


… dummerweise nicht portabel, da GCC bei reinterpret_cast in einen Aliasing-Schreikrampf verfällt. (Oder doch nicht, weil char immer aliasen darf?!) Die portable Lösung ist memcpy():

  short gr;
  memcpy(&gr, &r, 2);
  int bgr = gr | (b << 16);


Versteht zwar niemand mehr, aber immernoch perfekte Befehlsfolge! :)

Hey, wenn Visual C++ memcpy() so gut optimiert … warum dann nicht direkt alle drei Bytes optimieren lassen?!

  int bgr = 0;
  memcpy(&bgr, &r, 3);


… ganz einfach: Weil Visual C++ dann alles auf den Stack spillt, obwohl es optimierte 16+8-Loads durchführt.

  movzx  eax,word ptr [rdx]
  and    dword ptr [rsp+40h],0
  mov    word ptr [rsp+40h],ax
  mov    al,byte ptr [rdx+2]
  mov    byte ptr [rsp+42h],al
  mov    eax,dword ptr [rsp+40h]


Sechs Speicherzugriffe für drei Bytes – sportlich!

… jetzt wisst ihr bescheid, falls ihr das mal braucht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6349
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon Krishty » 21.03.2018, 18:26

Alle memmove()-Aufrufe durch memcpy() ersetzt – deutlich größeres Programm.

Visual C++ kann z.B. nicht erkennen, dass memmove() aus einem globalen Array in eine lokale Variable nicht aliasen kann und dadurch memcpy() gleicht. Schreibt man dann von Hand memcpy() hin, greift plötzlich der Optimizer und ersetzt den Funktionsaufruf durch mov-Befehle entsprechender Größen.

Torvalds-mäßig einfach überall memmove() zu nutzen und sich darauf zu verlassen, dass es der Compiler schon richten wird, klappt also nicht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6349
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon Krishty » 25.03.2018, 01:33

Visual C++ 2015 hatte einen schlimmen constexpr-Array-Bug. Es schien sowas zu sein wie „wenn das constexpr-Array Zeiger auf andere constexpr-Arrays enthält, wird das Alignment abgefuckt“ und sorgte dafür, dass in entsprechenden Arrays Müll landete.

Ich finde gerade keinen Bug Report, aber in 2017.6 ist es behoben. Falls ihr auf 2015 festsitzt, testet die Stellen mit constexpr-Arrays also besonders gut.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6349
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon Krishty » 30.03.2018, 11:58

Krishty hat geschrieben:Update von Visual Studio 15.5.5 auf 15.6.

[…]

optimize for speed: return ~0; erzeugt nun statt OR EAX, -1 (drei Bytes) MOV EAX, -1 (fünf Bytes).
Ich habe einen starken Verdacht, was für diese Veränderung ausschlaggebend war: Artificial Mind hatte doch letztes Jahr einen Artikel über Branchless Binary Search verlinkt. In dem war die Binärsuche mit GCC mehr als doppelt so schnell wie mit Visual C++. Die Ursache war:
The highlighted instruction “or r9, -1” on 4-th line is a cunning way of putting -1 into register: it takes only 4 bytes of machine code, while the obvious “mov r9, -1” takes 7 bytes of machine code. It is very similar to the “xor eax, eax” hack for zeroing a register, which every CPU understands today and handles during register renaming. Unfortunately, there is no special handling of instruction “or r9, -1” in Broadwell or other x86 architectures, so false dependency is created: CPU assumes that the new value of r9 register depends on its previous value, which is not set before that in the function. […] This explains why MSVC version is so much slower than that of GCC.
… und sie haben sich tatsächlich drum gekümmert!

Ich werde mal einen Kommentar schreiben, dass er die Tests wiederholen soll.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6349
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon Krishty » 02.04.2018, 13:24

Ich dekoriere gerade viel mit __declspec(noalias) – eigentlich eher zufällig, weil ich ein paar Clang/GCC-Warnungen wegkriegen möchte. (Dort entspricht es __attribute__((const)).

__declspec(noalias) hat anscheinend keine Wirkung für Funktionen, deren Quelltext vorliegt. Dafür zeigt es tatsächlich Wirkung mit externen Funktionen: Vor und nach dem Funktionsaufruf werden weniger Daten gespeichert und neu geladen. (Visual C++ müsste sonst annehmen, dass der externe Aufruf alle globalen Daten modifiziert und alles neu laden.)

Ich bin noch nicht ganz sicher, ob ich __declspec(noalias) auf sowas wie GetSysColor() oder GetClientRect() anwenden darf. Nach Microsofts Dokumentation:
noalias means that a function call does not modify or reference visible global state […].
[…] The visible global state is the set of all data that is not defined or referenced outside of the compilation scope, and their address is not taken. The compilation scope is all source files (/LTCG (Link-time Code Generation) builds) or a single source file (non-/LTCG build).
Das „modify“ trifft auf WinAPI-Aufrufe nicht zu, das „reference“ schon. Allerdings ist der referenzierte Speicher nicht Teil meines Programms und ändert sich nur über WinAPI-Aufrufe, die nicht noalias definiert sind.
noalias means that a function call […] only modifies the memory pointed to directly by pointer parameters (first-level indirections).
If a function is annotated as noalias, the optimizer can assume that, in addition to the parameters themselves, only first-level indirections of pointer parameters are referenced or modified inside the function.
Das trifft auf WinAPI-Aufrufe nicht zu, da sie z.B. interne Datenstrukturen und globale Variablen abfragen. Die sind aber nicht Teil des „Compilation Scope“ oben und die Dokumentation lässt offen, ob das nur für selbiges gilt.

Ich baue es jetzt erstmal für WinAPI & Co. ein, messe die Verbesserung, und prüfe, ob was kaputtgeht.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6349
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon Krishty » 04.04.2018, 21:03

Der erste Code Generation Bug ist behoben: https://developercommunity.visualstudio ... s-mix.html

Ich schätze, dass der Fix im Herbst ausgeliefert werden wird.

————

__m128 anzeigen ist wohl nicht wichtig genug ¯\_(ツ)_/¯

https://developercommunity.visualstudio ... meter.html

————

Krishty hat geschrieben:
This explains why MSVC version is so much slower than that of GCC.
… und sie haben sich tatsächlich drum gekümmert!
Ich werde mal einen Kommentar schreiben, dass er die Tests wiederholen soll.
Der Autor hat mir bestätigt, dass der Hinweis von ihm kam: https://developercommunity.visualstudio ... -whic.html

Vielleicht sollte ich zu meinem Debugger-Bug schlicht schreiben, dass GDB __m128 doppelt so gut anzeigen kann wie Visual Studio?
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6349
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon dot » 05.04.2018, 12:32

Ich hab auch mal wieder was: Da freut man sich so auf guaranteed copy elision und wenn man es dann endlich verwenden will, dann passiert sowas: https://developercommunity.visualstudio ... -user.html :(
Benutzeravatar
dot
Michael Kenzel
Establishment
 
Beiträge: 1629
Registriert: 06.03.2004, 19:10

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon Krishty » 06.04.2018, 21:33

Ich liebe es, wie subtil du sie darauf hinweist, dass ihre Werbung Bullshit ist :)
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6349
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon dot » 06.04.2018, 22:11

:mrgreen:
Benutzeravatar
dot
Michael Kenzel
Establishment
 
Beiträge: 1629
Registriert: 06.03.2004, 19:10

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon dot » 09.04.2018, 11:34

Das ging ja wieder mal fix :D

Sieht aus, dass es sich offenbar wirklich auszahlt den Fehler ordentlich zu isolieren. Der andere, der vermutlich den selben Bug schon vor einem Monat reported hat, ist immer noch "Under Investigation"...
Benutzeravatar
dot
Michael Kenzel
Establishment
 
Beiträge: 1629
Registriert: 06.03.2004, 19:10

Re: Sammelthread zu Visual C++’ Compiler

Beitragvon Krishty » 20.04.2018, 21:01

Krishty hat geschrieben:Der erste Code Generation Bug ist behoben: https://developercommunity.visualstudio ... s-mix.html

Ich schätze, dass der Fix im Herbst ausgeliefert werden wird.
Wow – der Fix ist seit gestern im Visual Studio Preview. Hut ab, das ist wirklich viel schneller als ich erwartet habe.

Mein zweiter Code Generation Bug ist auch markiert als Fixed – pending release, da rechne ich aber dann wirklich mit Release im Herbst.

Andererseits passiert auch immer wieder sowas wie dem armen Eric Lengyel: https://developercommunity.visualstudio ... n-bug.html
Bug gemeldet; sein Beispiel-Code wird gefixt, aber an anderer Stelle taucht er noch immer auf. (Wie mein CV-Funktions-Bug.)

Jetzt heult er auf Twitter statt ZFX’ Jammer-Thread vollzuspammen wie ein richtiger Mann.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6349
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy


Zurück zu Programmiersprachen, Quelltext und Bibliotheken

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 3 Gäste