[C++] Mikrooptimierungs-Log

Hier können Artikel, Tutorials, Bücherrezensionen, Dokumente aller Art, Texturen, Sprites, Sounds, Musik und Modelle zur Verfügung gestellt bzw. verlinkt werden.
Forumsregeln
Möglichst sinnvolle Präfixe oder die Themensymbole nutzen.

[C++] Mikrooptimierungs-Log

Beitragvon Krishty » 17.08.2012, 20:18

Hallo und willkommen zu Krishtys bonbonbunter Wunderwelt der Mikrooptimierungen!

Da ich für gewöhnlich nichts anderes mache, als völlig belanglosen Quelltext rundzulutschen statt mich wirklich fruchtbaren Fortschritten zu widmen, denke ich, dass ihr meine Mikroerrungenschaften vielleicht lesen und recyclen wollt. Darum präsentiere ich hier jedes Mal, wenn ich eine tolle, aber sinnfreie Optimierung entdecke (lies: in regelmäßigen Abständen) eine kleine Analyse. Wegen meiner begrenzten Zeit und Geduld kann ich hier leider nur Erfahrungen aus Visual C++ 2010 x64 auf einem Intel Core i7 anbieten; aber ich möchte euch ermutigen, es auch mit anderen Systemen zu testen!


Ich begrüße sogleich unseren ersten Gast, ein …

Schnelles min() / max() für Gleitkommazahlen durch SSE2

… und, um es kurz zu machen: Es ist eine Lüge. Die klassische Version mit dem bedingten Sprung:

    float max(float a, float b) {
        return a > b ? a : b;
    }


ist synthetisch auf einem Core i7 etwa 70 % schneller als der extra dafür abgestellte SSE-Befehl MAXSS mit Datenabhängigkeit:

    float max(float a, float b) {
        return _mm_cvtss_f32(_mm_max_ss(_mm_set_ss(a), _mm_set_ss(b)));
    }


Je unregelmäßiger der Sprung ist, desto schlechter schneidet MAXSS ab – mit richtig schön randomisierten Werten ist der Klassiker zuweilen doppelt so schnell. (Die Werte sind synthetisch; mir fällt aber kein Grund ein, warum es sich in realen Anwendungsfällen umkehren sollte.)

Warum? Laut Intel-Handbuch schlägt MAXSS mit glatt drei Takten zu Buche; also etwas mehr, als die beiden Befehle für bedingten Sprung mit angeschlossener Kopie zusammen. Auf AMD-Hardware ist mit nur zwei Takten zu rechnen, dort erwarte ich also ähnliche Leistung wie der Klassiker – aber eben auch keine Optimierung.

Man könnte jetzt argumentieren: Hey! Das ist doch eine SSE-Funktion, die ist für paralleles Maximum von vier floats ausgelegt! Nö, is’e nicht. Das wäre MAXPS, nicht MAXSS. Aber falls man zufällig ein Register voll gepackter Zahlen parat hat, ist erstere natürlich der roxx0r schlechthin und empfehlenswert, wie immer bei SIMD. Lest dazu unbedingt diesen Nachtrag!

Was haben wir heute gelernt? Manchmal ist es auch eine Optimierung, die Optimierung sein zu lassen. Für Skeptiker ist der Benchmark-Quelltext im Spoiler (vielleicht möchte ja jemand auf AMD-Hardware testen); für alle anderen: Kommentare sind willkommen, und falls euch keine einfallen, bis zur nächsten Episode!
Testfall; kompiliert mit VC 2010 (wie man an der fehlenden Autovektorisierung sieht); getestet auf Core i7:

    size_t const len = 768 * 1024 * 1024;
    auto arr = new float[len];
    auto rand = 0xBB234C27;
    for(size_t i = 0; i < len; ++i) {
        arr[i] = rand;
        rand += 0xCBDA77FC;
    }

    auto start = __rdtsc();

    float acc = 0.0f;
    for(size_t i = 0; i < len; i += 4) {
        acc += max(acc, arr[i+0]);
        acc += max(acc, arr[i+1]);
        acc += max(acc, arr[i+2]);
        acc += max(acc, arr[i+3]);
    }

    auto ticks = __rdtsc() - start;

    out << acc << "@" << ticks;
    __debugbreak();    float acc = 0.0f;
 xorps       xmm0,xmm0
    }


Klassiker:
    auto start = __rdtsc();
 lea         rcx,[rdi+8]
 mov         edx,0C000000h
        acc += max(acc, arr[i+0]);
 movss       xmm1,dword ptr [rcx-8]
 comiss      xmm0,xmm1
 jbe         main+0ADh (14000824Dh)
 movaps      xmm1,xmm0
 addss       xmm0,xmm1
        acc += max(acc, arr[i+1]);
 movss       xmm2,dword ptr [rcx-4]
 comiss      xmm0,xmm2
 jbe         main+0BEh (14000825Eh)
 movaps      xmm2,xmm0
 addss       xmm0,xmm2
        acc += max(acc, arr[i+2]);
 movss       xmm1,dword ptr [rcx]
 comiss      xmm0,xmm1
 jbe         main+0CEh (14000826Eh)
 movaps      xmm1,xmm0
 addss       xmm0,xmm1
        acc += max(acc, arr[i+3]);
 movss       xmm1,dword ptr [rcx+4]
 comiss      xmm0,xmm1
 jbe         main+0DFh (14000827Fh)
 movaps      xmm1,xmm0
 addss       xmm0,xmm1
    for(size_t i = 0; i < len; i += 4) {
 add         rcx,10h
 dec         rdx
 jne         main+0A0h (140008240h)
    }


Zeiten:

3248441883
3251520016
3285571953


MAXSS:

    float acc = 0.0f;
 xorps       xmm2,xmm2
    }

    auto start = __rdtsc();
 add         rdi,8
 mov         ecx,0C000000h
        acc += max(acc, arr[i+0]);
 movss       xmm0,dword ptr [rdi-8]
 movss       xmm1,xmm2
 maxss       xmm1,xmm0
 addss       xmm2,xmm1
        acc += max(acc, arr[i+1]);
 movss       xmm0,dword ptr [rdi-4]
 movss       xmm1,xmm2
 maxss       xmm1,xmm0
 addss       xmm2,xmm1
        acc += max(acc, arr[i+2]);
 movss       xmm0,dword ptr [rdi]
 movss       xmm1,xmm2
 maxss       xmm1,xmm0
 addss       xmm2,xmm1
        acc += max(acc, arr[i+3]);
 movss       xmm0,dword ptr [rdi+4]
 movss       xmm1,xmm2
 maxss       xmm1,xmm0
 addss       xmm2,xmm1
    for(size_t i = 0; i < len; i += 4) {
 add         rdi,10h
 dec         rcx
 jne         main+0A0h (13FF08240h)
    }


Zeiten:

5578030548
5539127893
5559210794
Zuletzt geändert von Krishty am 02.03.2015, 14:41, insgesamt 6-mal geändert.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6127
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: [C++] Mikrooptimierungs-Log

Beitragvon Krishty » 18.08.2012, 10:29

Willkommen zurück. Heute mal was Einfaches, unter der alten Prämisse: Gilt für Visual C++ 2010 x64; getestet mit einem Intel Core i7.

Verdoppeln einer Gleitkommazahl

Ich spüre schon, wie ihr in Ehrfurcht erstarrt. Doch so einfach das auch klingt – gerade im Umgang mit Gleitkommazahlen ist der Compiler oft übervorsichtig (meist mit Recht!), und wir können hier schön sehen, wie man der Maschine noch was vormachen kann.

Wir beginnen wieder mit dem Klassiker:

    float doubled(float x) {
        return 2.0f * x;
    }


In den Testfall aus Folge 1 eingebaut ergibt sich folgender Maschinentext:

 …
 movss       xmm3,dword ptr [__real@40000000 (13F303480h)]
 …
        acc += doubled(arr[i+0]);
 movss       xmm1,dword ptr [rcx-8]
 mulss       xmm1,xmm3
 …


und hier rollen sich dem geneigten Pedanten die Fußnägel auf. Was da im Speziellen passiert:
  • Der Compiler legt im Read-Only-Datenabschnitt der ausführbaren Datei eine Konstante 0x40000000 (die Integerrepräsentation des float-Werts 2.0f) an.
  • Diese Konstante wird in ein Register geladen.
  • Dieses Register wird mit einem Register, das x enthält, multipliziert.
Ein Bisschen viel Arbeit für eine Multiplikation, oder? Die Konstante ist zwar, nun ja, konstant, und wird deshalb sicher keine direkten Cache Misses oder Latenzen verursachen, aber sie wird beim Laden irgendeine andere Cache-Zeile des Programms verdrängen und damit einen indirekten Miss auslösen. Außerdem macht sie den Text länger und lächelt nie und ihre Mutter zieht sie komisch an. Versuchen wir es mal anders:

    float doubled(float x) {
        return x + x;
    }

 movss       xmm0,dword ptr [rcx-8]
 addss       xmm0,xmm0


Voilà – das Laden einer Konstanten und die Belegung eines Registers gespart. Die Multiplikation wurde durch eine Addition ersetzt, die laut Intel-Handbuch auch weniger Ausführungszeit benötigt. Eine hübsche Mikrooptimierung in Zeit und Raum.

Wie viel das Ganze schneller ist, kann ich nicht sagen. Der Testfall, den ich gebastelt habe, sagt 2 % voraus. Real wird es aber deutlich mehr sein, weil
  • das Laden der Konstante im Testfall von dem Compiler aus der Schleife herausbewegt wurde, also nicht mitgemessen, wurde;
  • mir nicht einfällt, wie ich mögliche Cache-Misses zuverlässig benchen soll; und
  • die Benchmark bei so fixen Operationen wahrscheinlich durch die Speicherbandbreite begrenzt wird (von der reinen Ausführungszeit der Berechnung her sollte es nun etwa doppelt so schnell sein, ist es aber nicht).
Kurz: Die 2 % sagen bloß aus, dass in einem stark speicherdurchsatzbegrenzten Fall eine Addition schneller ist als eine Multiplikation. Ob die Verbesserung real bloß zwei oder doch 100 % erreicht, steht in den Sternen.

Jedenfalls: Wenn beim nächsten Mal etwas verdoppelt werden muss, doubled() aufrufen statt 2.0 hinzuschreiben!

P.S.: Bringt aber nur bei Gleitkommazahlen was; Ganzzahltypen werden eh von jedem Nischen-Cmpiler besser optimiert.

Bis zum nächsten Mal!
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6127
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: [C++] Mikrooptimierungs-Log

Beitragvon Schrompf » 18.08.2012, 12:18

Bleib dran! Ich lese mir das gerne durch und komme darauf zurück, wenn es mal kritisch sein sollte.
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: 3626
Registriert: 26.02.2009, 00:44
Wohnort: Dresden
Benutzertext: Lernt nur selten dazu

Re: [C++] Mikrooptimierungs-Log

Beitragvon Krishty » 18.08.2012, 20:24

Schön zu hören :) Ich denke, ich werde dieses Wochenende noch zwei Beiträge rauskramen – ich habe nämlich was mit richtig Wirkung gefunden. Das wird’s dann morgen zur Krönung geben.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6127
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: [C++] Mikrooptimierungs-Log

Beitragvon Krishty » 18.08.2012, 20:28

Diesmal wird es wieder um einen SSE2-Befehl gehen. Bedenkt: Der Tipp wurde nur mit Visual C++ 2010 x64 auf einem Intel Core i7 getestet.

Schnellere Quadratwurzeln mit SSE2

Viele wissen nicht, dass SSE2 einen eigenen Befehl zum Berechnen von Quadratwurzeln mitbringt. Wessen CPU x64-kompatibel ist, oder in der zweiten Hälfte des ersten Jahrzehnts des ersten Jahrhunderts des dritten Jahrtausends produziert wurde, sollte ihn sich unbedingt mal angucken. Der Referenz-Benchmark wird erstmal umgebaut, um die Standardfunktion von Visual C++’ CRT zu nutzen:

    acc += sqrt(arr[i+0]);
    acc += sqrt(arr[i+1]);
    acc += sqrt(arr[i+2]);
    acc += sqrt(arr[i+3]);


Die Ausführungszeit nehmen wir als Referenz.

Schauen wir uns mal ein Stück des entstehenden Maschinentextes an:

        acc += sqrt(arr[i+3]);
 movd        xmm0,dword ptr [rdi-8]
 cvtps2pd    xmm0,xmm0
 call        sqrt (13F398E3Eh)
 unpcklps    xmm6,xmm6
 addsd       xmm0,xmm1


… und hier offenbart sich auch schon das große Problem: CVTPS2PD ist eine Konvertierung von float zu double. Die VC-CRT bietet nur eine double-Version der Funktion an; beim Ziehen jeder Wurzel wird der Wert also zu doppelter Präzision konvertiert; in doppelter Genauigkeit ausgerechnet; zu einfacher Genauigkeit zurückgerundet und dann weiterverarbeitet. Das geschieht außerdem in einer externen Funktion (wen der genaue Ablauf interessiert, der kann ja im Debugger durchgehen – u.a. werden die CPU-Features bestimmt und dementsprechend aus einer passenden Implementierung gewählt, allerdings keine, die wieder auf SQRTSD zurückgreift!) Fassen wir kurz zusammen:
  • unnötige Konvertierungen zwischen float und double sind böse
  • Sachen doppelt so genau wie nötig ausrechnen ist böse
  • Funktionsaufrufe sind böse
Der entsprechend große Vorteil des SSE2-Befehls ist, dass er auf float-Eingaben auch nur mit float-Genauigkeit arbeitet und dadurch viel Zeit spart. Ich persönlich wundere mich, warum der Befehl nicht stärker eingesetzt wird – anfangs habe ich einen Haken vermutet, konnte aber keinen finden (die Genauigkeit ist in Ordnung; die Verarbeitung von NaN, Unendlichkeiten und negativen Zahlen sieht IEEE-konform aus). Wer es weiß, teile es mir bitte mit!

Übrigens erzeugt Visual C++ bei mir minimal besseren Maschinentext, wenn ich den Wert als Referenz übergebe:

    float squareRootOf(
        float const & value // pass by reference: saves one useless 'xorps'
    ) {
        // • Copy the value to an XMM register using '_mm_set_ss()' (http://msdn.microsoft.com/en-us/library/0thxfyft).
        // • Compute the square root using '_mm_sqrt_ss()' (http://msdn.microsoft.com/en-us/library/ahfsc22d). This will
        //    generate a 'SQRTSS' instruction, which seems IEEE754-compliant (though I could not find any proof on that).
        // • Extract the result using '_mm_cvtss_f32()' (http://msdn.microsoft.com/en-us/library/bb514059).
        return _mm_cvtss_f32(_mm_sqrt_ss(_mm_set_ss(value)));
    }


 movss       xmm3,dword ptr [rdi-8]
 sqrtss      xmm3,xmm3
 addss       xmm0,xmm3


Sieht doch gleich viel besser aus – und verbraucht nur 56 % der Ausführungszeit, ist also fast doppelt so schnell!

Okay; das war minimal ungerecht: wenn sqrt() nur für double geschrieben ist, sollten wir es auch damit mal testen! In dem Fall benötigt die SSE2-Version ein zusätzliches Register und sieht folgendermaßen aus:

    double squareRootOf(
        double const & value // pass by reference: saves one useless 'xorpd'
    ) {
        // • Copy the value to an XMM register using '_mm_set_sd()' (http://msdn.microsoft.com/en-us/library/dksztbt9).
        // • A second register is required to pass a dummy value. Use 'mm_setzero_pd()' to zero it so any dependencies are
        //     broken (http://msdn.microsoft.com/en-us/library/3x8wktyc).
        // • Compute the square root using '_mm_sqrt_sd()' (http://msdn.microsoft.com/en-us/library/1994h1ay). This will
        //     generate a 'SQRTSD' instruction, which seems to be IEEE754-compliant (though I could not find any proof on that).
        // • Extract the result using '_mm_cvtsd_f64()' (http://msdn.microsoft.com/en-us/library/bb531421).
        return _mm_cvtsd_f64(_mm_sqrt_sd(_mm_setzero_pd(), _mm_set_sd(value)));
    }


Hier schneidet das handgeschriebene VC-CRT-sqrt() auf einem Core i7 exakt ein Prozent besser ab. Trotzdem würde ich mich für SQRTSD entscheiden: Obgleich ein Prozent langsamer, kommt es ohne Sprünge in Fremdbibliotheken und mit weniger Maschinentext aus. Ich gehe stark davon aus, dass es die handgeschriebene Version nur in synthetischen Benchmarks schneller ist, und dass SQRTSD durch die deutlich bessere Lokalität (ein einziger Befehl auf zwei Registern gegenüber einem KiB entfernten Maschinentexts, der zehn Register verschlingt) in allen tatsächlichen Anwendungen die schnellere Wahl sein wird.

Falls man zufällig vier Skalare in einem Register herumliegen hat, ist die SSE-Version übrigens sowieso vier- bis achtmal so schnell. Spätestens dann sollte man also zugreifen!

Das war also eine Mikrooptimierung, bei der wir deutliche Wirkung nachweisen konnten. Werden wir das beim nächsten Mal wieder schaffen? *cliffhang*
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6127
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: [C++] Mikrooptimierungs-Log

Beitragvon eXile » 18.08.2012, 23:18

Krishty hat geschrieben:        // • Compute the square root using '_mm_sqrt_ss()' (http://msdn.microsoft.com/en-us/library/ahfsc22d). This will
        //    generate a 'SQRTSS' instruction, which seems IEEE754-compliant (though I could not find any proof on that).

Ha, da kann ich aber liefern:
Intel® 64 and IA-32 Architectures Software Developer’s Manual, Seite 494, http://download.intel.com/design/proces ... 253665.pdf hat geschrieben:SSE/SSE2/SSE3 extensions are 100% compatible with the IEEE Standard 754 for
Binary Floating-Point Arithmetic, satisfying all of its mandatory requirements (when
the flush-to-zero or denormals-are-zeros modes are not enabled).
Intel® 64 and IA-32 Architectures Software Developer’s Manual, Seite 376, http://download.intel.com/design/proces ... 253665.pdf hat geschrieben:IEEE 754 Compliance of SSE4.1 Floating-Point Instructions
The six SSE4.1 instructions that perform floating-point arithmetic are:
  • DPPS
  • DPPD
  • ROUNDPS
  • ROUNDPD
  • ROUNDSS
  • ROUNDSD
Dot Product operations are not specified in IEEE-754.

Und SSE 4.2 enthält keine Floating-Point-Operationen. Damit sind alle SSE-Befehle mit Floating-Point-Zusammenhang IEEE-754-konform.

Ich hätte als nächstes auf der Wunschliste eine Abhandlung zur inversen Quadratwurzel; als Lesestoff siehe hier. ;)
Benutzeravatar
eXile
Establishment
 
Beiträge: 1136
Registriert: 28.02.2009, 14:27

Re: [C++] Mikrooptimierungs-Log

Beitragvon dot » 18.08.2012, 23:23

MSDN hat geschrieben:Using the true intrinsic forms implies loss of IEEE exception handling, and loss of _matherr and errno functionality; the latter implies loss of ANSI conformance.

Daher generiert MSVC unter /fp:precise Calls in die Libraryfunktionen (unter /fp:precise sind math intrisincs rein prinzipiell disabled). Mit /fp:fast bekommst du den von dir gewünschten SSE Code. Aus irgendeinem Grund scheint MSVC allerdings sqrtps statt sqrtss zu verwenden...
Benutzeravatar
dot
Michael Kenzel
Establishment
 
Beiträge: 1601
Registriert: 06.03.2004, 19:10

Re: [C++] Mikrooptimierungs-Log

Beitragvon Krishty » 18.08.2012, 23:33

@eXile: Super, danke! Damit fällt mir echt ein Stein vom Herzen.

Die inverse Quadratwurzel ist ein schöner Wemmser. Wie im verlinkten Thread schon in der höchstbewerteten Antwort steht: Die Anweisung selber ist furchtbar ungenau; und wie man sie auf annähernd (aber nicht ganz!) so hohe Genauigkeit wie SQRTSS kriegt, beschreiben Intel selber schon im Paper. Außerdem gibt es anscheinend Probleme mit Sonderwerten, beispielsweise bei sqrt(0).

Vor allem sehe ich aber schon, dass es dabei nicht bei eine Stunde Ausprobieren, Testfälle hinhacken, benchen und fertig bleiben wird – ich würde mindestens alle 2^32 float-Werte einmal durchjagen und auf ULPs vergleichen und die Anweisungen stundenlang umsortieren und … ;) Ich reihe ein, aber erstmal ganz hinten, sorry.

@dot: Perfekt – klingt absolut einleuchtend. Ich arbeite eigentlich nie mit /fp:fast, weil ich jede Menge Unendlichkeiten und NaN rumschiebe. Das hätte ich wohl wirklich testen sollen …
Warum SQRTPS genutzt wird, weiß ich auch nicht. Vielleicht wird damit nochmal zusätzlich eine Abhängigkeit aufgebrochen … ich kann es leider dieses Wochenende nicht testen, weil ich vor einer anderen CPU sitze.
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6127
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: [C++] Mikrooptimierungs-Log

Beitragvon Krishty » 19.08.2012, 13:52

Hallo und willkommen zurück! Nachdem wir bisher nur *Mikro*optimierungen durchgeackert haben, werde ich heute mal einen schönen fetten Brocken präsentieren – nämlich eine Mikrooptimierung, die einen Compiler-Fehler in Visual C++ 2010 umschifft:

Überflüssige Destruktoren weglassen

Und direkt ab dafür – wir beginnen mit meiner plumpen 3D-Vektor-Datenstruktur:

    template <
        typename Scalar
    > struct Vector<Scalar, 3> {
        Scalar x;
        Scalar y;
        Scalar z;
    };


Schlicht und schnell, wie ich es liebe. Um es vorweg zu nehmen: Das ist die optimierte Version.

Den Fehler, um den es geht, habe ich zufällig entdeckt, weil einige Leute – früher auch ich – gern der Explizität halber einen Destruktor definieren, selbst, wenn die Klasse an sich keinen braucht. Dabei ist es egal, wie viel sinnloses Zeug der Destruktor erledigt (einige Leute nullen z.B. gern alle Attribute vor der Zerstörung – halloooohooo! Nach dem Destruktor ist das Objekt weg und nie mehr zugreifbar; es ist egal, was drinsteht!) oder ob er einfach nur leer ist. Aber schauen wir einfach mal, was passiert, wenn wir der Datenstruktur dies hinzufügen:

    ~Vector() { }

Die folgende unscheinbare Zeile (sie lässt sich effizenter berechnen, aber das ist Stoff für eine spätere Folge)

    auto const relativeVelocity = dotProductOf(normalized(direction), normalized(velocity));

ohne Destruktor:

    movss       xmm0,xmm15
    movaps      xmm1,xmm13
    movaps      xmm5,xmm12
    movaps      xmm4,xmm8
    movaps      xmm6,xmm11
    movaps      xmm7,xmm10
    sqrtss      xmm0,xmm0
    mulss       xmm1,xmm13
    movaps      xmm3,xmm10
    divss       xmm5,xmm0
    divss       xmm6,xmm0
    divss       xmm4,xmm0
    movaps      xmm0,xmm9
    mulss       xmm0,xmm9
    mulss       xmm7,xmm10
    addss       xmm7,xmm0
    movaps      xmm0,xmm9
    addss       xmm7,xmm1
    movaps      xmm1,xmm13
    movss       xmm2,xmm7
    sqrtss      xmm2,xmm7
    divss       xmm1,xmm2
    divss       xmm0,xmm2
    divss       xmm3,xmm2
    mulss       xmm1,xmm6
    mulss       xmm0,xmm4
    mulss       xmm3,xmm5
    addss       xmm3,xmm0
    addss       xmm3,xmm1


und mit:

    mov         rdx,rsi
    [color=FF2010]call        Math::normalized<3> (13F188580h)[/color]
    lea         rdx,[direction]
    [color=FF2010]call        Math::normalized<3> (13F188580h)[/color]
    movss       xmm2,dword ptr [rax+4]
    mulss       xmm2,dword ptr [rbx+4]
    movss       xmm0,dword ptr [rax]
    mulss       xmm0,dword ptr [rbx]
    addss       xmm2,xmm0
    movss       xmm0,dword ptr [rax+8]
    mulss       xmm0,dword ptr [rbx+8]
    addss       xmm0,xmm2


Hier sind deutliche Unterschiede zu erkennen. Was ist passiert?

Offenbar genießen Klassen, die einen Destruktor besitzen, in VC++ einen Sonderstatus (unwindable) – sogar, wenn der D’tor garnichts tut; es geht nur darum, dass einer definiert wurde. (Der Fehler ist Microsoft bekannt und soll in einer zukünftigen Version behoben werden. Offenbar hat Microsoft meinen Bug Report mittlerweile gelöscht. Yay! Wer VC++ 2010 und VC++ 2012 nebeneinander installiert hat, ist eingeladen, die Kompilate zu vergleichen!)

Insbesondere wird für alle Funktionen, die einen unwindable Rückgabewert besitzen, Inlining deaktiviert. Wird also einer häufig genutzten Klasse – wie in diesem Fall dem 3D-Vektor – ein D’tor hinzugefügt, kommt das damit gleich, für die meistgenutzten Funktionen Inlining zu deaktivieren. Auch die kleinste Popelfunktion wird nun nicht mehr geinlinet:

    Vector<float, 3> const operator / (
        Vector<float, 3> const &    dividend,
        Scalar const &              divisor
    ) {
        Vector<float, 3> const result = {
            dividend.x / divisor,
 movss       xmm2,dword ptr [r8]
 movss       xmm0,dword ptr [rdx]
            dividend.y / divisor,
 movss       xmm1,dword ptr [rdx+4]
            dividend.z / divisor
        };
        return result;
 mov         rax,rcx
 divss       xmm0,xmm2
 divss       xmm1,xmm2
 movss       dword ptr [rcx],xmm0
 movss       xmm0,dword ptr [rdx+8]
 movss       dword ptr [rcx+4],xmm1
 divss       xmm0,xmm2
 movss       dword ptr [rcx+8],xmm0
    }
 ret


Mit Inlining würden hier bloß drei DIVSS-Befehle stehen.

Wie viel das tatsächlich bewirkt, hängt von der Anwendung ab. Ich habe hier eine Direct3D-Anwendung mit hoher CPU-Rechenlast; im oben beschriebenen Fall – der 3D-Vektor-Klasse einen überflüssigen D’tor verpassen – fällt die Aktualisierungsrate von 57 auf 50 Bilder pro Sekunde, oder um 14 %. Das ist, wohlgemerkt, keine Messung einer einzelnen Funktion, sondern des gesamten Programms. Die heutige Mikrooptimierung hat also eine Wirkung, für die sich einige Leute ein Bein abreißen würden!

Was wir daraus lernen: Auch Compiler sind fehleranfällige, komplexe Programme; und wie sich Quelltext auf sie auswirkt, ist schwer vorherzusagen. Generell sollte alles immer so einfach wie gerade möglich gehalten werden – VC++ gerät durch einen überflüssigen D’tor aus dem Tritt; bei anderen Compilern könnte es sonstwas sein. Raus mit allem, was nicht unbedingt nötig ist!

Und damit bis zum nächsten Wochenende!
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6127
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: [C++] Mikrooptimierungs-Log

Beitragvon eXile » 19.08.2012, 17:50

Krishty hat geschrieben:Der Fehler ist Microsoft bekannt und soll in einer zukünftigen Version behoben werden. Offenbar hat Microsoft meinen Bug Report mittlerweile gelöscht. Yay! Wer VC++ 2010 und VC++ 2012 nebeneinander installiert hat, ist eingeladen, die Kompilate zu vergleichen!

Am Arsch. (Zu Dokumentationszwecken: Die Seite war hier, und da stand irgendwas von kein schwerwiegender Fehler, wird frühestens in Visual C++ 2042 gefixt, usw. :evil:)

Damit wir wissen, worüber wir hier reden: Ich habe folgenden Code unter Visual C++ 2010 und Visual C++ 2012 RC (mit allen Updates jeweils) auf x64 im Release kompiliert, und als einzige Änderung /fp:fast in den Projekteinstellungen vorgenommen.

Code: Ansicht erweitern :: Alles auswählen
#include <iostream>
#include <cmath>

template <
        typename Scalar,
        size_t Dimension
> struct Vector;

template <
        typename Scalar
> struct Vector<Scalar, 3> {
        Scalar x;
        Scalar y;
        Scalar z;

//      ~Vector() {};
};

template <
        typename Scalar
> Scalar dotProductOf(
        Vector<Scalar, 3> a,
        Vector<Scalar, 3> b
) {
        return a.x * b.x + a.y * b.y + a.z * b.z;
}

template <
        typename Scalar
> Vector<Scalar, 3> normalized(
        Vector<Scalar, 3> v
) {
        Scalar norm = sqrt(dotProductOf(v, v));

        Vector<Scalar, 3> const result = {
                v.x / norm,
                v.y / norm,
                v.z / norm
        };

        return result;
}

float test(
        float d1,
        float d2,
        float d3,
        float v1,
        float v2,
        float v3
) {
        Vector<float, 3> direction = { d1, d2, d3 };
        Vector<float, 3> velocity = { v1, v2, v3 };
        auto const relativeVelocity = dotProductOf(normalized(direction), normalized(velocity));

        return relativeVelocity;
}

int main(int argc, char * argv[])
{
        float d1, d2, d3, v1, v2, v3;

        std::cin >> d1 >> d2 >> d3 >> v1 >> v2 >> v3;
        std::cout << test(d1, d2, d3, v1, v2, v3) << std::endl;

        return 0;
}
 

Visual C++ 2010 ohne ~Vector():
Code: Ansicht erweitern :: Alles auswählen
test:
000000013FAC1000  mov         rax,rsp  
000000013FAC1003  sub         rsp,68h  
000000013FAC1007  movaps      xmmword ptr [rax-18h],xmm6  
000000013FAC100B  movaps      xmmword ptr [rax-28h],xmm7  
000000013FAC100F  movaps      xmmword ptr [rax-38h],xmm8  
000000013FAC1014  movaps      xmm8,xmm0  
000000013FAC1018  movaps      xmmword ptr [rax-48h],xmm9  
000000013FAC101D  movaps      xmmword ptr [rax-58h],xmm10  
000000013FAC1022  movss       xmm9,dword ptr [__real@3f800000 (13FAC21E0h)]  
000000013FAC102B  movaps      xmm10,xmm1  
000000013FAC102F  movaps      xmmword ptr [rsp],xmm11  
000000013FAC1034  movaps      xmm11,xmm2  
000000013FAC1038  movss       xmm2,dword ptr [v2]  
000000013FAC1041  movaps      xmm7,xmm9  
000000013FAC1045  movaps      xmm4,xmm2  
000000013FAC1048  movaps      xmm0,xmm3  
000000013FAC104B  mulss       xmm0,xmm3  
000000013FAC104F  mulss       xmm4,xmm2  
000000013FAC1053  addss       xmm0,xmm4  
000000013FAC1057  movss       xmm4,dword ptr [v3]  
000000013FAC1060  movaps      xmm5,xmm4  
000000013FAC1063  mulss       xmm5,xmm4  
000000013FAC1067  addss       xmm0,xmm5  
000000013FAC106B  sqrtss      xmm1,xmm0  
000000013FAC106F  movaps      xmm0,xmm10  
000000013FAC1073  divss       xmm7,xmm1  
000000013FAC1077  mulss       xmm0,xmm10  
000000013FAC107C  movaps      xmm1,xmm11  
000000013FAC1080  movaps      xmm5,xmm7  
000000013FAC1083  movaps      xmm6,xmm7  
000000013FAC1086  mulss       xmm5,xmm2  
000000013FAC108A  mulss       xmm1,xmm11  
000000013FAC108F  mulss       xmm6,xmm3  
000000013FAC1093  movaps      xmm2,xmm8  
000000013FAC1097  mulss       xmm2,xmm8  
000000013FAC109C  mulss       xmm7,xmm4  
000000013FAC10A0  addss       xmm2,xmm0  
000000013FAC10A4  addss       xmm2,xmm1  
000000013FAC10A8  sqrtss      xmm0,xmm2  
000000013FAC10AC  divss       xmm9,xmm0  
000000013FAC10B1  movaps      xmm1,xmm9  
000000013FAC10B5  movaps      xmm0,xmm9  
000000013FAC10B9  mulss       xmm9,xmm11  
000000013FAC10BE  mulss       xmm0,xmm10  
000000013FAC10C3  mulss       xmm9,xmm7  
000000013FAC10C8  mulss       xmm1,xmm8  
000000013FAC10CD  movaps      xmm7,xmmword ptr [rax-28h]  
000000013FAC10D1  movaps      xmm8,xmmword ptr [rax-38h]  
000000013FAC10D6  movaps      xmm10,xmmword ptr [rax-58h]  
000000013FAC10DB  mulss       xmm0,xmm5  
000000013FAC10DF  mulss       xmm1,xmm6  
000000013FAC10E3  movaps      xmm6,xmmword ptr [rax-18h]  
000000013FAC10E7  movaps      xmm11,xmmword ptr [rsp]  
000000013FAC10EC  addss       xmm0,xmm1  
000000013FAC10F0  addss       xmm0,xmm9  
000000013FAC10F5  movaps      xmm9,xmmword ptr [rax-48h]  
000000013FAC10FA  add         rsp,68h  
000000013FAC10FE  ret  

Visual C++ 2010 mit ~Vector():
Code: Ansicht erweitern :: Alles auswählen
test:
000000013F311000  push        rbp  
000000013F311002  mov         rbp,rsp  
000000013F311005  sub         rsp,60h  
000000013F311009  movss       dword ptr [rbp-20h],xmm0  
000000013F31100E  movss       xmm0,dword ptr [rbp+30h]  
000000013F311013  movss       dword ptr [rbp-30h],xmm3  
000000013F311018  lea         rdx,[rbp-40h]  
000000013F31101C  lea         rcx,[rbp-10h]  
000000013F311020  movss       dword ptr [rbp-2Ch],xmm0  
000000013F311025  movss       dword ptr [rbp-1Ch],xmm1  
000000013F31102A  movss       xmm1,dword ptr [rbp+38h]  
000000013F31102F  mov         rax,qword ptr [rbp-30h]  
000000013F311033  mov         qword ptr [rbp-40h],rax  
000000013F311037  movss       dword ptr [rbp-28h],xmm1  
000000013F31103C  movss       dword ptr [rbp-18h],xmm2  
000000013F311041  mov         eax,dword ptr [rbp-28h]  
000000013F311044  mov         dword ptr [rbp-38h],eax  
000000013F311047  call        normalized<float> (13F311180h)  
000000013F31104C  lea         rdx,[rbp-30h]  
000000013F311050  mov         ecx,dword ptr [rax]  
000000013F311052  mov         dword ptr [rbp-40h],ecx  
000000013F311055  mov         ecx,dword ptr [rax+4]  
000000013F311058  mov         eax,dword ptr [rax+8]  
000000013F31105B  mov         dword ptr [rbp-38h],eax  
000000013F31105E  mov         rax,qword ptr [rbp-20h]  
000000013F311062  mov         dword ptr [rbp-3Ch],ecx  
000000013F311065  mov         qword ptr [rbp-30h],rax  
000000013F311069  mov         eax,dword ptr [rbp-18h]  
000000013F31106C  lea         rcx,[rbp-20h]  
000000013F311070  mov         dword ptr [rbp-28h],eax  
000000013F311073  call        normalized<float> (13F311180h)  
000000013F311078  mov         ecx,dword ptr [rax]  
000000013F31107A  movss       xmm0,dword ptr [rbp-40h]  
000000013F31107F  movss       xmm1,dword ptr [rbp-3Ch]  
000000013F311084  movss       xmm2,dword ptr [rbp-38h]  
000000013F311089  mov         dword ptr [rbp-30h],ecx  
000000013F31108C  mov         ecx,dword ptr [rax+4]  
000000013F31108F  mov         eax,dword ptr [rax+8]  
000000013F311092  mulss       xmm0,dword ptr [rbp-30h]  
000000013F311097  mov         dword ptr [rbp-2Ch],ecx  
000000013F31109A  mov         dword ptr [rbp-28h],eax  
000000013F31109D  mulss       xmm1,dword ptr [rbp-2Ch]  
000000013F3110A2  mulss       xmm2,dword ptr [rbp-28h]  
000000013F3110A7  addss       xmm0,xmm1  
000000013F3110AB  addss       xmm0,xmm2  
000000013F3110AF  add         rsp,60h  
000000013F3110B3  pop         rbp  
000000013F3110B4  ret  

Visual C++ 2012 RC ohne ~Vector():
Code: Ansicht erweitern :: Alles auswählen
test:
000007F68F2F1240  mov         rax,rsp  
000007F68F2F1243  sub         rsp,68h  
000007F68F2F1247  movss       xmm4,dword ptr [v2]  
000007F68F2F1250  movaps      xmmword ptr [rax-18h],xmm6  
000007F68F2F1254  movaps      xmmword ptr [rax-28h],xmm8  
000007F68F2F1259  movaps      xmmword ptr [rax-38h],xmm9  
000007F68F2F125E  movaps      xmmword ptr [rax-48h],xmm10  
000007F68F2F1263  movaps      xmm9,xmm0  
000007F68F2F1267  mulss       xmm4,xmm4  
000007F68F2F126B  movss       xmm10,dword ptr [__real@3f800000 (07F68F2F3400h)]  
000007F68F2F1274  movaps      xmmword ptr [rax-58h],xmm11  
000007F68F2F1279  movaps      xmm11,xmm1  
000007F68F2F127D  movaps      xmm8,xmm10  
000007F68F2F1281  xorps       xmm1,xmm1  
000007F68F2F1284  movaps      xmmword ptr [rsp],xmm12  
000007F68F2F1289  movaps      xmm12,xmm2  
000007F68F2F128D  movaps      xmm2,xmm0  
000007F68F2F1290  mulss       xmm2,xmm9  
000007F68F2F1295  movaps      xmm6,xmm3  
000007F68F2F1298  movaps      xmm0,xmm11  
000007F68F2F129C  mulss       xmm0,xmm11  
000007F68F2F12A1  mulss       xmm6,xmm3  
000007F68F2F12A5  addss       xmm2,xmm0  
000007F68F2F12A9  addss       xmm6,xmm4  
000007F68F2F12AD  movss       xmm4,dword ptr [v3]  
000007F68F2F12B6  xorps       xmm0,xmm0  
000007F68F2F12B9  movaps      xmm5,xmm4  
000007F68F2F12BC  mulss       xmm5,xmm4  
000007F68F2F12C0  addss       xmm6,xmm5  
000007F68F2F12C4  sqrtss      xmm1,xmm6  
000007F68F2F12C8  divss       xmm8,xmm1  
000007F68F2F12CD  movaps      xmm1,xmm12  
000007F68F2F12D1  mulss       xmm1,xmm12  
000007F68F2F12D6  movaps      xmm6,xmm8  
000007F68F2F12DA  movaps      xmm5,xmm8  
000007F68F2F12DE  mulss       xmm5,dword ptr [v2]  
000007F68F2F12E7  mulss       xmm6,xmm3  
000007F68F2F12EB  mulss       xmm8,xmm4  
000007F68F2F12F0  addss       xmm2,xmm1  
000007F68F2F12F4  sqrtss      xmm0,xmm2  
000007F68F2F12F8  divss       xmm10,xmm0  
000007F68F2F12FD  movaps      xmm1,xmm10  
000007F68F2F1301  movaps      xmm0,xmm10  
000007F68F2F1305  mulss       xmm10,xmm12  
000007F68F2F130A  mulss       xmm0,xmm11  
000007F68F2F130F  mulss       xmm10,xmm8  
000007F68F2F1314  mulss       xmm1,xmm9  
000007F68F2F1319  movaps      xmm8,xmmword ptr [rax-28h]  
000007F68F2F131E  movaps      xmm9,xmmword ptr [rax-38h]  
000007F68F2F1323  movaps      xmm11,xmmword ptr [rax-58h]  
000007F68F2F1328  mulss       xmm0,xmm5  
000007F68F2F132C  mulss       xmm1,xmm6  
000007F68F2F1330  movaps      xmm6,xmmword ptr [rax-18h]  
000007F68F2F1334  movaps      xmm12,xmmword ptr [rsp]  
000007F68F2F1339  addss       xmm0,xmm1  
000007F68F2F133D  addss       xmm0,xmm10  
000007F68F2F1342  movaps      xmm10,xmmword ptr [rax-48h]  
000007F68F2F1347  add         rsp,68h  
000007F68F2F134B  ret  

Visual C++ 2012 RC mit ~Vector():
Code: Ansicht erweitern :: Alles auswählen
test:
000007F6B5CC1240  push        rbp  
000007F6B5CC1242  mov         rbp,rsp  
000007F6B5CC1245  sub         rsp,80h  
000007F6B5CC124C  movss       dword ptr [rbp-50h],xmm0  
000007F6B5CC1251  movss       xmm0,dword ptr [rbp+30h]  
000007F6B5CC1256  movss       dword ptr [rbp-60h],xmm3  
000007F6B5CC125B  movaps      xmmword ptr [rsp+70h],xmm6  
000007F6B5CC1260  lea         rdx,[rbp-40h]  
000007F6B5CC1264  lea         rcx,[rbp-60h]  
000007F6B5CC1268  movaps      xmmword ptr [rsp+60h],xmm7  
000007F6B5CC126D  movaps      xmmword ptr [rsp+50h],xmm8  
000007F6B5CC1273  movss       dword ptr [rbp-5Ch],xmm0  
000007F6B5CC1278  movss       dword ptr [rbp-4Ch],xmm1  
000007F6B5CC127D  movss       xmm1,dword ptr [rbp+38h]  
000007F6B5CC1282  mov         rax,qword ptr [rbp-60h]  
000007F6B5CC1286  mov         qword ptr [rbp-40h],rax  
000007F6B5CC128A  movss       dword ptr [rbp-58h],xmm1  
000007F6B5CC128F  movss       dword ptr [rbp-48h],xmm2  
000007F6B5CC1294  mov         eax,dword ptr [rbp-58h]  
000007F6B5CC1297  mov         dword ptr [rbp-38h],eax  
000007F6B5CC129A  call        normalized<float> (07F6B5CC1800h)  
000007F6B5CC129F  lea         rdx,[rbp-40h]  
000007F6B5CC12A3  lea         rcx,[rbp-50h]  
000007F6B5CC12A7  movss       xmm6,dword ptr [rax]  
000007F6B5CC12AB  movss       xmm8,dword ptr [rax+4]  
000007F6B5CC12B1  movss       xmm7,dword ptr [rax+8]  
000007F6B5CC12B6  mov         rax,qword ptr [rbp-50h]  
000007F6B5CC12BA  mov         qword ptr [rbp-40h],rax  
000007F6B5CC12BE  mov         eax,dword ptr [rbp-48h]  
000007F6B5CC12C1  mov         dword ptr [rbp-38h],eax  
000007F6B5CC12C4  call        normalized<float> (07F6B5CC1800h)  
000007F6B5CC12C9  mulss       xmm8,dword ptr [rax+4]  
000007F6B5CC12CF  mulss       xmm6,dword ptr [rax]  
000007F6B5CC12D3  mulss       xmm7,dword ptr [rax+8]  
000007F6B5CC12D8  addss       xmm8,xmm6  
000007F6B5CC12DD  movaps      xmm6,xmmword ptr [rsp+70h]  
000007F6B5CC12E2  addss       xmm8,xmm7  
000007F6B5CC12E7  movaps      xmm7,xmmword ptr [rsp+60h]  
000007F6B5CC12EC  movaps      xmm0,xmm8  
000007F6B5CC12F0  movaps      xmm8,xmmword ptr [rsp+50h]  
000007F6B5CC12F6  add         rsp,80h  
000007F6B5CC12FD  pop         rbp  
000007F6B5CC12FE  ret  

Ohne ein Assembler-Magier zu sein, hier die Kurzversion:
  • Visual C++ 2012 RC sortiert ein paar Befehle um
  • Visual C++ 2012 RC generiert um eine Instruktion längeren Code bei nicht vorhandenem Destruktor, ansonsten fünf Instruktionen kürzeren Code. Ziemlich egal.
  • Visual C++ 2012 RC generiert noch immer Funktionsaufrufe bei leerem Destruktor :evil:

Bild
Benutzeravatar
eXile
Establishment
 
Beiträge: 1136
Registriert: 28.02.2009, 14:27

Re: [C++] Mikrooptimierungs-Log

Beitragvon eXile » 20.08.2012, 20:43

Nachtrag: Gilt auch für die Visual Studio Ultimate 2012 90-day trial. Wird damit in der distributierten Version nicht gefixt sein.

So haben wir das ja mal wieder gerne, einfach den Bugreport löschen aber nichts fixen. Werden sich vermutlich mit „Connect-Datenbank migrieren“ oder so herausreden. :evil:
Benutzeravatar
eXile
Establishment
 
Beiträge: 1136
Registriert: 28.02.2009, 14:27

Re: [C++] Mikrooptimierungs-Log

Beitragvon Schrompf » 21.08.2012, 15:08

Diskussion zur Nutzung des Visual Studio-C++-Compilers hier herausgetrennt und dorthin verschoben: viewtopic.php?f=4&t=2503
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: 3626
Registriert: 26.02.2009, 00:44
Wohnort: Dresden
Benutzertext: Lernt nur selten dazu

Re: [C++] Mikrooptimierungs-Log

Beitragvon Krishty » 25.08.2012, 16:57

Heute geht es mal einem wirklich hinterlistigen kleinen Detail an den Kragen:

Gleitkommatypen konsistent halten

Was ist suboptimal am folgenden (semantisch richtigen) Stück Quelltext?

    float value = foo();
    if(0.1 <= value) {
        bar(value);
    }


Unter x86 garnichts, unter x64 schon: Das if vergleicht eine double-Konstante mit einem float-Wert.

Unter x86 ist das egal, weil die FPU intern sowieso mit 80-Bit-Gleitkommazahlen arbeitet, value wird also sowieso in 80 Bits vorliegen und 0.1 wird beim Laden implizit zu 80 Bits konvertiert. Der Speicherdurchsatz der Ladeoperation wird zwar 32 Bits größer sein, das hat aber bei einer einzelnen Konstante keine Auswirkung auf die Geschwindigkeit.

x64 wird value hingegen tatsächlich nur als 32-Bit-Gleitkommazahl in den SSE-Registern halten und 0.1 in 64 Bits laden. Weil es keine native Anweisung gibt, die eine 32- mit einer 64-Bit-Gleitkommazahl vergleicht, wird value zu 64 hochkonvertiert werden. (Der umgekehrte Weg, 0.1 auf 32 Bits zu reduzieren, wird vom Compiler nicht genommen werden, weil dabei signifikande Genauigkeit verloren ginge. Selbst, falls foo() intern mit 64 Bits gerechnet und nur das Ergebnis auf 32 Bits reduziert hat, wird diese Reduktion nicht wegoptimiert werden, weil sie beobachtbare und möglicherweise beabsichtigte Wirkung aufs Programm hat.)

Für die Konvertierung und den Vergleich emittiert Visual C++ die folgenden Anweisungen:

    unpcklps    xmm3,xmm3
    cvtps2pd    xmm0,xmm3
    comisd      xmm0,xmm2


Dabei verteilt UNPCKLPS die Zahl (grob gesagt) aufs ganze Register und wird wahrscheinlich eingesetzt, um das Register von Überbleibseln vorheriger Anweisungen zu reinigen, die sonst bei der Konvertierung eine Floating-Point Exception auslösen könnten, weil die Konvertierung nur auf die zwei unteren Werte eines Registers zugleich durchführbar ist (die skalare Version wäre langsamer). Es schlägt mit einem Takt zu Buche. CVTPS2PD führt die eigentliche Konvertierung durch. Die Latenz beträgt 2–3 Takte. Danach wird verglichen (zwei Takte, für 32-Bit-Gleitkommazahlen wäre die Latenz aber gleich).

Damit hat ein fehlendes f hinter der Konstanten das if um drei Takte verzögert. Ähnliches passiert eigentlich bei jeder Rechnung, die 32- und 64-Bit-Genauigkeit durcheinanderwirft. Wer ausschließlich mit 32-Bit-Genauigkeit arbeitet, könnte also mal einen Blick über alle Konstanten und die Typen der Zwischenergebnisse riskieren.

Leider habe ich genau dafür keine kohärente Lösung gefunden. Es ist dank Regulärer Ausdrücke [eeew!] möglich, in Visual C++ alle double-Literale im Quelltext aufzulisten:
  • STRG+Shift+F
  • [0-9]+[.][0-9]+[^f]
  • Find Options -> Use -> Regular Expressions
Dann eben alle durchgehen und prüfen, ob sie legitim sind. Der Compiler sollte eigentlich eine vollständige Liste aller Konvertierungen zusammenstellen können; da ranzukommen habe ich aber noch keinen Weg gefunden. Bis dahin bleibt nur: Weiterhin immer fleißig den Maschinentext prüfen!
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6127
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Re: [C++] Mikrooptimierungs-Log

Beitragvon dot » 25.08.2012, 17:26

Vielleicht sollte man noch erwähnen: Das Problem hat man natürlich auch unter x86, sobald man SSE aktiviert. Unter x64 ist SSE nur implizit schon an.

Einer der Gründe, wieso ich das hab:

Code: Ansicht erweitern :: Alles auswählen
namespace math
{
  namespace constants
  {
    template <typename T>
    T one();
    template <>
    inline float one<float>() { return 1.0f; }
    template <>
    inline double one<double>() { return 1.0; }
    template <>
    inline long double one<long double>() { return 1.0; }

    template <typename T>
    T zero();
    template <>
    inline float zero<float>() { return 0.0f; }
    template <>
    inline double zero<double>() { return 0.0; }
    template <>
    inline long double zero<long double>() { return 0.0; }

    template <typename T>
    T pi();
    template <>
    inline float pi<float>() { return 3.1415926535897932384626434f; }
    template <>
    inline double pi<double>() { return 3.1415926535897932384626434; }
    template <>
    inline long double pi<long double>() { return 3.1415926535897932384626434; }
  }
}


Vom beliebten #define PI 3.14 ist auf jeden Fall abzuraten, nicht nur weil es ein böses Makro ist. Auch ein const double pi = 3.14 führt potentiell zu ineffizientem Code...
Benutzeravatar
dot
Michael Kenzel
Establishment
 
Beiträge: 1601
Registriert: 06.03.2004, 19:10

Re: [C++] Mikrooptimierungs-Log

Beitragvon Krishty » 25.08.2012, 17:43

Im Augenblick habe ich es ebenfalls so. Funktionen werden aber manchmal auch nicht richtig optimiert – zugegebenermaßen hatte ich noch nie den Fall, in dem Visual C++ eine Funktion return 3.14…; nicht optimiert hätte, aber überrascht wäre ich von sowas nicht. Ich plane schon seit langem dies, und hatte nur nie die Zeit, es durchzuziehen:

    template <> struct Constants<float> {
        static float const pi;
    };

    …

    float const Constants<float>::pi = 3.14…f;


Wo wir gerade bei Konstanten sind: Als nächstes sollte ich zeigen, wie man INF und NaN hard-codet. Dabei gibt es nämlich auch Visual C++-Optimizer-Ausfälle, dass einem die Kinnlade runterklappt.

dot hat geschrieben:Vielleicht sollte man noch erwähnen: Das Problem hat man natürlich auch unter x86, sobald man SSE aktiviert. Unter x64 ist SSE nur implizit schon an.
Nicht immer – bloß, weil man SSE aktiviert, wird SSE nicht für alles benutzt. Meiner Erfahrung nach macht der Compiler weiterhin alle Arithmetik mit der FPU und nur Datenschieberei und Intrinsics mit SSE. Das ist ja das große Problem daran, mit Visual C++ auf einem Atom zu programmieren (wo SSE gegenüber der FPU stark zu bevorzugen ist, man es Visual C++ aber niemals entlockt kriegt).
seziert Ace Combat, Driver, und S.T.A.L.K.E.R.   —   rendert Sterne
Benutzeravatar
Krishty
Establishment
 
Beiträge: 6127
Registriert: 26.02.2009, 12:18
Benutzertext: state is the enemy

Nächste

Zurück zu Artikel, Tutorials und Materialien

Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 1 Gast