2016-08-08 5 views
5

Betrachten Sie den folgenden Code ein:Ist es möglich, eine Kompilierungszeit "Referenz" zu einer Variablen zu haben?

Matrix4x4 perspective(const ViewFrustum &frustum) { 
    float l = frustum.l; 
    float r = frustum.r; 
    float b = frustum.b; 
    float t = frustum.t; 
    float n = frustum.n; 
    float f = frustum.f; 

    return { 
     { 2 * n/(r - l), 0,    (r + l)/(r - l), 0      }, 
     { 0,    2 * n/(t - b), (t + b)/(t - b), 0      }, 
     { 0,    0,    -((f + n)/(f - n)), -(2 * n * f/(f - n)) }, 
     { 0,    0,    -1,     0      } 
    }; 
} 

Um die Lesbarkeit des Aufbaus der Matrix zu verbessern, ich habe entweder eine Kopie der Werte aus der Stumpfes Struktur zu machen, oder Verweise auf sie. Ich brauche aber auch keine Kopien oder Indirektionen.

Ist es möglich, eine Art "Referenz" zu haben, die zur Kompilierungszeit aufgelöst wird, ähnlich wie ein symbolischer Link. Es hätte den gleichen Effekt wie:

Matrix4x4 perspective(const ViewFrustum &frustum) { 
    #define l frustum.l; 
    #define r frustum.r; 
    #define b frustum.b; 
    #define t frustum.t; 
    #define n frustum.n; 
    #define f frustum.f; 

    return { 
     { 2 * n/(r - l), 0,    (r + l)/(r - l), 0      }, 
     { 0,    2 * n/(t - b), (t + b)/(t - b), 0      }, 
     { 0,    0,    -((f + n)/(f - n)), -(2 * n * f/(f - n)) }, 
     { 0,    0,    -1,     0      } 
    }; 

    #undef l 
    #undef r 
    #undef b 
    #undef t 
    #undef n 
    #undef f 
} 

Ohne den Präprozessor (oder ist es akzeptabel?). Ich nehme an, es ist nicht wirklich notwendig oder könnte in diesem speziellen Fall vermieden werden, indem diese 6 Werte direkt zu einer Funktion gemacht werden (obwohl es ein bisschen irritierend wäre, die Funktion so zu bezeichnen - aber selbst dann könnte ich es machen eine Inline-Proxy-Funktion).

Aber ich habe mich nur gefragt, ob das überhaupt möglich ist? Ich konnte nichts dergleichen finden. Ich denke, es würde sich als nützlich erweisen, beschreibende Namen lokal zu kürzen, die sehr oft verwendet werden, ohne die ursprünglichen Namen wirklich zu verlieren.

+1

Sie meinen wie eine Referenz? 'float & l (frustum.l)' usw.? – John3136

+0

@ John3136 ja, aber die eigentliche Referenz wird nicht benötigt –

+5

Wie auch immer Sie es betrachten, es ist völlig eine Mikro-Optimierung, ob Sie eine Kopie erstellen oder eine Referenz verwenden. Der Compiler ist intelligent genug, um Verweise auf diese Variablen zu optimieren. Machen Sie das Beste für die Lesbarkeit. –

Antwort

11

Obligatorischer Haftungsausschluss: Nicht vorzeitig optimieren.

Lassen Sie mich Ihre naive perspective Funktion vergleichen,

float l = frustum.l; 
float r = frustum.r; 
float b = frustum.b; 
float t = frustum.t; 
float n = frustum.n; 
float f = frustum.f; 

Mit define ‚s und @ Sam Varshavchik Lösung mit Referenzen enthält.

Wir gehen davon aus, dass unser Compiler optimiert und zumindest anständig optimiert.

Baugruppenausgabe für alle drei Versionen: https://godbolt.org/g/G06Bx8.

Sie können feststellen, dass Referenz und definieren Versionen genau die gleichen sind - wie erwartet. Aber naiv unterscheidet sich sehr. Es lädt zunächst alle Werte aus dem Speicher:

movss (%rdi), %xmm2   # xmm2 = mem[0],zero,zero,zero 
    movss 4(%rdi), %xmm1   # xmm1 = mem[0],zero,zero,zero 
    movss 8(%rdi), %xmm0   # xmm0 = mem[0],zero,zero,zero 
    movss %xmm0, 12(%rsp)   # 4-byte Spill 
    movss 12(%rdi), %xmm0   # xmm0 = mem[0],zero,zero,zero 
    movss %xmm0, 8(%rsp)   # 4-byte Spill 
    movss 16(%rdi), %xmm3   # xmm3 = mem[0],zero,zero,zero 
    movaps %xmm3, 16(%rsp)   # 16-byte Spill 
    movss 20(%rdi), %xmm0 

Und dann verweist nie wieder die %rdi (frustrum) Speicher. Verweise und Definitionen von Versionen hingegen laden die Werte so, wie sie benötigt werden.

Dies geschieht, weil die Umsetzung des Vector4 Konstruktor aus dem Optimierer verborgen ist, und es kann nicht, dass Konstruktor übernimmt frustrum nicht ändern, so dass es muss Einsatz Lasten, auch wenn solche Lasten redundant sind.

So, naive Version kann sogar schneller als "optimiert" werden, unter bestimmten Umständen.

+0

@Byteventurer, wie für 'clang', wurden sie nicht gleich, aber der Compiler lädt nicht mehr jedes Mal Werte aus dem Speicher: https: //godbolt.org/g/hQfASp. Also ist es besser und (ich denke) wird zur Laufzeit so schnell wie naiv sein. – deniss

+0

Okay, vielen Dank! : D (für die Aufzeichnung, als ich den Kommentar löschte - ich fragte, ob es einen Unterschied macht, wenn Matrix als einfaches Array implementiert ist) –

+1

@Byteventurer: Hoffentlich haben Sie triviale Funktionen wie diese Konstruktoren im Header definiert, im Gegensatz zu Deniss Beispiel. Linktime-Optimierung kann dies beheben, aber es ist eine schlechte Idee, triviale Funktionen zu haben, die zur Kompilierzeit nicht sichtbar sind. Compiler können nicht viel davon annehmen, was eine Funktion geändert haben könnte, wenn sie die Definition nicht sehen können, aber wenn sie sie sehen können, können sie genau herausfinden, was eine Funktion macht, auch wenn sie nicht inline ist. –

11

Nun, das ist, was C++ Referenzen sind für:

const float &l = frustum.l; 
const float &r = frustum.r; 
const float &b = frustum.b; 
const float &t = frustum.t; 
const float &n = frustum.n; 
const float &f = frustum.f; 

Die meisten modernen Compiler die Referenzen optimieren heraus, und verwenden Sie die Werte aus dem frustum Objekt wörtlich, in den folgenden Ausdruck, indem Sie die Referenzen bei der Kompilierung Lösung -Zeit.

+1

Wird der Compiler auch die Variablen optimieren, wenn sie keine Referenzen sind - nur Kopien? – Rakete1111

+0

Ich sehe, so dass die Referenzen nicht tatsächlich verwendet werden? Keine Umleitung? –

+0

Ihr Code wird nicht kompiliert, es sei denn Sie 'const' qualifizieren Ihre Float-Referenzen (' frustum' ist const). –

4

Im Allgemeinen können Sie einfache Referenzen verwenden, solange Sie sich im lokalen Bereich befinden. Moderne Compiler "sehen durch" und behandeln sie einfach als Alias ​​(beachten Sie, dass dies auch für Zeiger gilt).

Wenn man sich jedoch mit kleinen Dingen beschäftigt, ist das Kopieren auf eine lokale Variable, wenn überhaupt, von Vorteil. frustnum.r ist eine Schicht der indirekten Weg (frustnum ist eigentlich ein Zeiger unter der Haube), so dass der Zugriff ist teurer als es vielleicht scheint, und wenn Sie Funktionsaufrufe in der Mitte Ihrer Funktion der Compiler möglicherweise nicht in der Lage, das zu beweisen Der Wert ändert sich nicht, daher muss der Zugriff möglicherweise wiederholt werden.

Lokale Variablen stattdessen sind normalerweise direkt auf dem Stapel (billig) oder gerade in den Registern (am billigsten), und am wichtigsten, da sie normalerweise keine Interaktion mit "dem Äußeren" haben, hat der Compiler eine leichtere Zeit über zu argumentieren sie, so kann es mit Optimierungen aggressiver sein; Wenn diese Berechnungen tatsächlich ausgeführt werden, werden diese Werte sowieso in Registern und auf dem Stapel kopiert.

Also gehen Sie voran und verwenden Sie Kopien, im schlimmsten Fall wird der Compiler wahrscheinlich das gleiche tun, im besten Fall können Sie es bei der Optimierung von Sachen helfen.

+0

Ja, ich dachte darüber nach, aber ich war mir nicht sicher, danke :) –

Verwandte Themen