Ich möchte wissen, ob "if-Anweisungen" in Shadern (Vertex / Fragment / Pixel ...) die Shader-Leistung wirklich verlangsamen. Zum Beispiel:
Ist es besser, dies zu verwenden:
vec3 output;
output = input*enable + input2*(1-enable);
anstatt dies zu verwenden:
vec3 output;
if(enable == 1)
{
output = input;
}
else
{
output = input2;
}
In einem anderen Forum wurde darüber gesprochen (2013): http://answers.unity3d.com/questions/442688/shader-if-else-performance.html Hier sagen die Jungs, dass die If-Aussagen wirklich schlecht sind für die Leistung des Shaders.
Auch hier wird darüber gesprochen, wie viel in den if / else-Anweisungen (2012) enthalten ist: https://www.opengl.org/discussion_boards/showthread.php/177762-Performance-alternative-for-if-(-)
Vielleicht sind die Hardware oder der Shadercompiler jetzt besser und sie beheben irgendwie dieses (möglicherweise nicht vorhandene) Leistungsproblem.
BEARBEITEN:
In diesem Fall ist enable beispielsweise eine einheitliche Variable und wird immer auf 0 gesetzt:
if(enable == 1) //never happens
{
output = vec4(0,0,0,0);
}
else //always happens
{
output = calcPhong(normal, lightDir);
}
Ich denke, hier haben wir einen Zweig im Shader, der den Shader verlangsamt. Ist das korrekt?
Ist es sinnvoller, zwei verschiedene Shader wie einen für den anderen und einen für den if-Teil zu erstellen?
if
Aussage. Die Gründe dafür sind zweierlei: Sie erhalten keine verschlungenen Codekonstrukte ohne Beweise dafür, dass sie besser sind, und was noch wichtiger ist, der Compiler erkennt Ihre Absichten eher und optimiert das Problem.Antworten:
Was ist mit Shadern, das möglicherweise sogar
if
Leistungsprobleme bei Anweisungen verursacht? Es hat damit zu tun, wie Shader ausgeführt werden und woher GPUs ihre massive Rechenleistung beziehen.Separate Shader-Aufrufe werden normalerweise parallel ausgeführt, wobei dieselben Anweisungen gleichzeitig ausgeführt werden. Sie führen sie einfach auf verschiedenen Sätzen von Eingabewerten aus. Sie teilen sich Uniformen, haben aber unterschiedliche interne Register. Ein Begriff für eine Gruppe von Shadern, die alle dieselbe Abfolge von Operationen ausführen, ist "Wellenfront".
Das potenzielle Problem bei jeder Form der bedingten Verzweigung besteht darin, dass sie all das vermasseln kann. Es bewirkt, dass unterschiedliche Aufrufe innerhalb der Wellenfront unterschiedliche Codesequenzen ausführen müssen. Dies ist ein sehr teurer Prozess, bei dem eine neue Wellenfront erstellt, Daten darauf kopiert usw. werden müssen.
Es sei denn ... es nicht.
Wenn die Bedingung beispielsweise von jedem Aufruf in der Wellenfront übernommen wird, ist keine Laufzeitdivergenz erforderlich. Als solche sind die Kosten für
if
nur die Kosten für die Überprüfung einer Bedingung.Nehmen wir also an, Sie haben einen bedingten Zweig und nehmen an, dass alle Aufrufe in der Wellenfront denselben Zweig haben. Es gibt drei Möglichkeiten für die Art des Ausdrucks in dieser Bedingung:
uniform
Werte). Der Wert des Ausdrucks ist jedoch zur Kompilierungszeit nicht bekannt. Der Compiler kann also statisch sicher sein, dass Wellenfronten dadurch niemals unterbrochen werdenif
, aber der Compiler kann nicht wissen, welcher Zweig verwendet wird.Unterschiedliche Hardware kann unterschiedliche Verzweigungstypen ohne Divergenz verarbeiten.
Selbst wenn eine Bedingung von verschiedenen Wellenfronten übernommen wird, kann der Compiler den Code so umstrukturieren, dass keine tatsächliche Verzweigung erforderlich ist . Sie haben ein gutes Beispiel gegeben:
output = input*enable + input2*(1-enable);
entspricht funktional derif
Aussage. Ein Compiler könnte erkennen, dass einif
zum Setzen einer Variablen verwendet wird, und somit beide Seiten ausführen. Dies geschieht häufig bei dynamischen Bedingungen, bei denen die Körper der Zweige klein sind.So gut wie die gesamte Hardware kann
var = bool ? val1 : val2
ohne Abweichungen umgehen . Dies war bereits im Jahr 2002 möglich.Da dies sehr hardwareabhängig ist, ... hängt es von der Hardware ab. Es gibt jedoch bestimmte Epochen von Hardware, die betrachtet werden können:
Desktop, Pre-D3D10
Dort ist es ein bisschen der wilde Westen. Der NVIDIA-Compiler für solche Hardware war dafür berüchtigt, solche Bedingungen zu erkennen und Ihren Shader tatsächlich neu zu kompilieren, wenn Sie Uniformen wechselten, die sich auf solche Bedingungen auswirkten.
Im Allgemeinen stammen in dieser Ära etwa 80% der "nie verwendeten
if
Aussagen". Aber auch hier ist es nicht unbedingt wahr.Sie können eine Optimierung der statischen Verzweigung erwarten. Sie können hoffen, dass eine statisch einheitliche Verzweigung keine zusätzliche Verlangsamung verursacht (obwohl die Tatsache, dass NVIDIA dachte, die Neukompilierung wäre schneller als die Ausführung, es zumindest für ihre Hardware unwahrscheinlich macht). Eine dynamische Verzweigung kostet Sie jedoch etwas, selbst wenn alle Aufrufe dieselbe Verzweigung haben.
Compiler dieser Ära geben ihr Bestes, um Shader so zu optimieren, dass einfache Bedingungen einfach ausgeführt werden können. Zum Beispiel
output = input*enable + input2*(1-enable);
ist dies etwas, das ein anständiger Compiler aus Ihrer entsprechendenif
Anweisung generieren könnte .Desktop, Post-D3D10
Hardware dieser Ära ist im Allgemeinen in der Lage, statisch einheitliche Verzweigungsanweisungen mit geringer Verlangsamung zu verarbeiten. Bei der dynamischen Verzweigung kann es zu einer Verlangsamung kommen oder nicht.
Desktop, D3D11 +
Hardware dieser Ära ist so gut wie garantiert in der Lage, dynamisch einheitliche Bedingungen mit geringen Leistungsproblemen zu bewältigen . In der Tat muss es nicht einmal dynamisch einheitlich sein; Solange alle Aufrufe innerhalb derselben Wellenfront denselben Pfad einschlagen, werden Sie keinen signifikanten Leistungsverlust feststellen.
Beachten Sie, dass einige Hardware aus der vorherigen Epoche dies wahrscheinlich auch tun könnte. Aber hier ist es fast sicher, dass es wahr ist.
Mobil, ES 2.0
Willkommen zurück im wilden Westen. Im Gegensatz zum Pre-D3D10-Desktop ist dies hauptsächlich auf die enorme Varianz der ES 2.0-Hardware zurückzuführen. Es gibt so viele Dinge, die mit ES 2.0 umgehen können, und alle funktionieren sehr unterschiedlich.
Die statische Verzweigung wird wahrscheinlich optimiert. Ob Sie jedoch durch statisch einheitliche Verzweigung eine gute Leistung erzielen, hängt stark von der Hardware ab.
Mobil, ES 3.0+
Hardware ist hier eher ausgereift und leistungsfähiger als ES 2.0. Daher können Sie davon ausgehen, dass statisch einheitliche Zweige relativ gut ausgeführt werden. Und manche Hardware kann wahrscheinlich dynamische Zweige wie moderne Desktop-Hardware verarbeiten.
quelle
min
undstep
so optimieren, dass sie nicht voneinander abweichen, oder?step(a, b) * c -> (a >= b) ? c : 0
Es hängt stark von der Hardware und dem Zustand ab.
Wenn Ihr Zustand einheitlich ist: Machen Sie sich keine Sorgen, lassen Sie den Compiler damit umgehen. Wenn Ihre Bedingung etwas Dynamisches ist (wie ein Wert, der aus einem Attribut berechnet oder aus einer Textur oder etwas anderem abgerufen wird), ist dies komplizierter.
Für den letzteren Fall müssen Sie so ziemlich testen und bewerten, da dies von der Komplexität des Codes in den einzelnen Zweigen und der Konsistenz der Zweigentscheidung abhängt.
Wenn beispielsweise einer der Zweige zu 99% aus dem Fall besteht und das Fragment verwirft, möchten Sie höchstwahrscheinlich die Bedingung beibehalten. Wenn OTOH in Ihrem einfachen Beispiel oben
enable
eine dynamische Bedingung ist, ist die arithmetische Auswahl möglicherweise besser.Wenn Sie keinen eindeutigen Fall wie den oben genannten haben oder nicht für eine feste bekannte Architektur optimieren, ist es wahrscheinlich besser, wenn der Compiler dies für Sie herausfindet.
quelle