Bei dem Versuch, die Leistung meiner Kollisionserkennungsklasse zu verbessern, stellte ich fest, dass ~ 80% der Zeit, die an der GPU verbracht wurde, für if / else-Bedingungen aufgewendet wurde, nur um die Grenzen für die Eimer herauszufinden, die sie durchlaufen sollte.
Etwas präziser:
Jeder Thread erhält eine ID, mit dieser ID holt er sein Dreieck aus dem Speicher (jeweils 3 Ganzzahlen) und mit diesen 3 seine Scheitelpunkte (jeweils 3 Floats).
Anschließend werden die Scheitelpunkte in ganzzahlige Gitterpunkte (derzeit 8 x 8 x 8) umgewandelt und in die Dreiecksgrenzen dieses Gitters umgewandelt
Um die 3 Punkte in Grenzen umzuwandeln, werden die Min / Max jeder Dimension unter jedem der Punkte ermittelt
Da in der von mir verwendeten Programmiersprache ein Minmax fehlt, habe ich selbst einen erstellt, der folgendermaßen aussieht:
procedure MinMax(a, b, c):
local min, max
if a > b:
max = a
min = b
else:
max = b
min = a
if c > max:
max = c
else:
if c < min:
min = c
return (min, max)
Im Durchschnitt sollten es also 2,5 * 3 * 3 = 22,5 Vergleiche sein, was viel mehr Zeit in Anspruch nimmt als die tatsächlichen Dreieck-Kanten-Schnittpunkttests (etwa 100 * 11-50 Anweisungen).
Tatsächlich stellte ich fest, dass die Vorberechnung der erforderlichen Buckets auf der CPU (Single Threaded, keine Vektorisierung), das Stapeln in einer GPU-Ansicht zusammen mit der Bucket-Definition und das Ausführen von ~ 4 zusätzlichen Lesevorgängen pro Thread durch die GPU sechsmal schneller war als der Versuch die Grenzen vor Ort herauszufinden. (Beachten Sie, dass sie vor jeder Ausführung neu berechnet werden, da es sich um dynamische Netze handelt.)
Warum ist der Vergleich auf einer GPU so schrecklich langsam?
quelle
Antworten:
GPUs sind SIMD-Architekturen. In SIMD-Architekturen muss jeder Befehl für jedes Element ausgeführt werden, das Sie verarbeiten. (Es gibt eine Ausnahme von dieser Regel, aber es hilft selten).
In Ihrer
MinMax
Routine muss also nicht nur jeder Aufruf alle drei Verzweigungsbefehle abrufen (auch wenn im Durchschnitt nur 2,5 ausgewertet werden), sondern jede Zuweisungsanweisung nimmt auch einen Zyklus in Anspruch (auch wenn sie nicht tatsächlich "ausgeführt" wird). ).Dieses Problem wird manchmal als Thread-Divergenz bezeichnet . Wenn Ihr Computer über 32 SIMD-Ausführungsspuren verfügt, verfügt er nur über eine einzige Abrufeinheit. (Hier bedeutet der Begriff "Thread" im Grunde genommen "SIMD-Ausführungsspur".) Intern hat also jede SIMD-Ausführungsspur ein "Ich bin aktiviert / deaktiviert" -Bit, und die Zweige manipulieren dieses Bit tatsächlich nur. (Die Ausnahme ist, dass an dem Punkt, an dem jede SIMD-Spur deaktiviert wird, die Abrufeinheit im Allgemeinen direkt zur "else" -Klausel springt.)
In Ihrem Code führt also jede SIMD-Ausführungsspur Folgendes aus:
Es kann vorkommen, dass bei einigen GPUs diese Konvertierung von Bedingungen in Prädikation langsamer ist, wenn die GPU dies selbst tut. Wie von @ PaulA.Clayton hervorgehoben, können
if (c) x = y else x = z
Sie möglicherweise bessere Ergebnisse erzielen, wenn Ihre Programmiersprache und -architektur über eine vorgegebene bedingte Verschiebungsoperation verfügt (insbesondere eine der Formulare ). (Aber wahrscheinlich nicht viel besser).Außerdem ist es nicht
c < min
erforderlich , die Bedingung innerhalbelse
von zu platzierenc > max
. Es spart Ihnen sicherlich nichts und (da die GPU es automatisch in Prädikation konvertieren muss) kann es tatsächlich weh tun, wenn es in zwei verschiedenen Bedingungen verschachtelt ist.quelle