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:
- Kompilierungszeit statisch. Der bedingte Ausdruck basiert vollständig auf Konstanten zur Kompilierungszeit. Als solches wissen Sie anhand des Codes, welche Zweige verwendet werden. So ziemlich jeder Compiler behandelt dies als Teil der grundlegenden Optimierung.
- Statisch gleichmäßige Verzweigung. Die Bedingung basiert auf Ausdrücken, die Dinge beinhalten, von denen zur Kompilierungszeit bekannt ist, dass sie konstant sind (insbesondere Konstanten und
uniform
Werte). Der Wert des Ausdrucks ist jedoch zur Kompilierungszeit nicht bekannt. Der Compiler kann also statisch sicher sein, dass Wellenfronten dadurch niemals unterbrochen werden if
, aber der Compiler kann nicht wissen, welcher Zweig verwendet wird.
- Dynamische Verzweigung. Der bedingte Ausdruck enthält andere Begriffe als Konstanten und Uniformen. Hier kann ein Compiler nicht a priori sagen, ob eine Wellenfront aufgelöst wird oder nicht. Ob dies geschehen muss, hängt von der Laufzeitauswertung des Bedingungsausdrucks ab.
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 der if
Aussage. Ein Compiler könnte erkennen, dass ein if
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 entsprechenden if
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.