Was kostet der Zustandswechsel?

25

Programmierer sollten eine ziemlich gute Vorstellung von den Kosten bestimmter Operationen haben: zum Beispiel die Kosten eines Befehls auf der CPU, die Kosten eines L1-, L2- oder L3-Cache-Fehlschlags, die Kosten eines LHS.

Wenn es um Grafiken geht, ist mir klar, dass ich kaum eine Ahnung habe, was sie sind. Ich denke, wenn wir sie nach Kosten ordnen, sind Zustandsänderungen so etwas wie:

  1. Wechsel der Shader-Uniform.
  2. Aktiver Vertex Buffer Change.
  3. Aktive Änderung der Textureinheit.
  4. Änderung des aktiven Shader-Programms.
  5. Aktiver Bildpufferwechsel.

Aber das ist eine sehr grobe Faustregel, die möglicherweise nicht einmal richtig ist, und ich habe keine Ahnung, wie hoch die Größenordnungen sind. Wenn wir versuchen, Einheiten, ns, Taktzyklen oder die Anzahl der Anweisungen zu setzen, wie viel reden wir dann?

Julien Guertault
quelle

Antworten:

26

Die meisten Daten, die ich über die relativen Kosten verschiedener Zustandsänderungen gesehen habe, stammen von Cass Everitt und John McDonalds Vortrag über die Reduzierung des OpenGL-API-Overheads ab Januar 2014. Der Vortrag beinhaltete diese Folie (um 31:55 Uhr):

Relative Kosten von Zustandsänderungen

Der Vortrag gibt keine weiteren Informationen darüber, wie sie dies gemessen haben (oder sogar, ob sie die CPU- oder GPU-Kosten messen oder beides!). Aber zumindest entspricht es der herkömmlichen Weisheit: Änderungen an Renderzielen und Shader-Programmen sind die teuersten, am wenigsten an einheitlichen Aktualisierungen, mit Vertex-Puffern und Texturänderungen irgendwo in der Mitte. Der Rest ihres Vortrags hat auch eine Menge interessanter Erkenntnisse über die Reduzierung des Overheads für Zustandsänderungen.

Nathan Reed
quelle
2
Ich wähle diese Antwort aus, da sie Größenordnungen angibt, die meiner Frage bisher am nächsten kommen, obwohl die erwähnte Quelle nicht viele Erklärungen liefert.
Julien Guertault
27

Die tatsächlichen Kosten einer bestimmten Zustandsänderung variieren mit so vielen Faktoren, dass eine allgemeine Antwort nahezu unmöglich ist.

Erstens kann jede Statusänderung möglicherweise sowohl CPU-seitige als auch GPU-seitige Kosten verursachen. Die CPU-Kosten können je nach Treiber und Grafik-API vollständig im Haupt-Thread oder teilweise in einem Hintergrund-Thread bezahlt werden.

Zweitens können die GPU-Kosten vom Arbeitsaufwand im Flug abhängen. Moderne GPUs sind sehr gut ausgelastet und lieben es, viel Arbeit auf einmal im Flug zu haben. Die größte Verlangsamung ist das Abwürgen der Pipeline, sodass alles, was sich gerade im Flug befindet, vor einer Änderung des Status in den Ruhestand gehen muss. Was kann einen Pipeline-Stillstand verursachen? Nun, es hängt von deiner GPU ab!

Das, was Sie tatsächlich wissen müssen, um die Leistung hier zu verstehen, ist: Was müssen der Treiber und die GPU tun, um Ihre Statusänderung zu verarbeiten? Dies hängt natürlich von Ihrer GPU und auch von Details ab, die ISVs häufig nicht öffentlich teilen. Es gibt jedoch einige allgemeine Grundsätze .

GPUs sind in der Regel in ein Frontend und ein Backend unterteilt. Das Frontend verarbeitet einen Strom von Befehlen, die vom Treiber generiert werden, während das Backend die gesamte eigentliche Arbeit erledigt. Wie ich schon sagte, das Backend liebt es, viel Arbeit im Flug zu haben, aber es braucht einige Informationen, um Informationen über diese Arbeit zu speichern (möglicherweise vom Frontend ausgefüllt). Wenn Sie genügend kleine Chargen kicken und das gesamte Silizium verbrauchen, um die Arbeit zu verfolgen, muss das Frontend blockieren, auch wenn viele ungenutzte Pferdestärken herumstehen. Ein Prinzip hier: Je mehr Statusänderungen (und kleine Draws), desto wahrscheinlicher ist es, dass Sie das GPU-Backend aushungern lassen .

Während eine Zeichnung tatsächlich verarbeitet wird, führen Sie im Grunde nur Shader-Programme aus, die Speicherzugriffe durchführen, um Ihre Uniformen, Ihre Vertex-Pufferdaten, Ihre Texturen abzurufen, aber auch die Kontrollstrukturen, die den Shader-Einheiten mitteilen, wo Ihre Vertex-Puffer und Deine Texturen sind. Und die GPU hat auch Caches vor diesen Speicherzugriffen. Wenn Sie also neue Uniformen oder neue Textur- / Pufferbindungen auf die GPU werfen, wird wahrscheinlich ein Cache-Fehler auftreten, wenn diese zum ersten Mal gelesen werden müssen. Ein weiteres Prinzip: Die meisten Statusänderungen verursachen einen GPU-Cache-Miss. (Dies ist am sinnvollsten, wenn Sie konstante Puffer selbst verwalten: Wenn Sie konstante Puffer zwischen den einzelnen Ziehungen beibehalten, bleiben diese mit größerer Wahrscheinlichkeit auf der GPU im Cache.)

Ein großer Teil der Kosten für Statusänderungen für Shader-Ressourcen ist die CPU-Seite. Wann immer Sie einen neuen konstanten Puffer festlegen, kopiert der Treiber höchstwahrscheinlich den Inhalt dieses konstanten Puffers in einen Befehlsstrom für die GPU. Wenn Sie eine einzelne Uniform festlegen, verwandelt der Fahrer diese sehr wahrscheinlich in einen großen konstanten Puffer hinter Ihrem Rücken. Deshalb muss er den Versatz für diese Uniform im konstanten Puffer nachschlagen, den Wert in den konstanten Puffer kopieren und dann den konstanten Puffer markieren so schmutzig, dass es vor dem nächsten Aufruf von draw in den Befehlsstrom kopiert werden kann. Wenn Sie eine neue Textur oder einen neuen Scheitelpunktpuffer binden, kopiert der Treiber wahrscheinlich eine Kontrollstruktur für diese Ressource. Wenn Sie eine diskrete GPU in einem Multitasking-Betriebssystem verwenden, muss der Treiber außerdem jede Ressource nachverfolgen, die Sie verwenden, und wenn Sie damit beginnen, damit der Kernel s Der GPU-Speichermanager kann sicherstellen, dass der Speicher für diese Ressource im VRAM der GPU gespeichert ist, wenn die Auslosung erfolgt. Prinzip:Statusänderungen veranlassen den Treiber, den Speicher zu verschieben, um einen minimalen Befehlsstrom für die GPU zu generieren.

Wenn Sie den aktuellen Shader ändern, verursachen Sie wahrscheinlich einen GPU-Cache-Fehler (sie haben auch einen Anweisungs-Cache!). Grundsätzlich sollte die CPU-Arbeit auf das Einfügen eines neuen Befehls in den Befehlsstrom beschränkt sein, der "use the shader" lautet. In Wirklichkeit gibt es jedoch eine ganze Reihe von Shader-Kompilierungen, mit denen man sich befassen muss. GPU-Treiber kompilieren Shader sehr häufig träge, selbst wenn Sie den Shader im Voraus erstellt haben. Diesbezüglich relevanter ist jedoch, dass einige Status nicht von Haus aus von der GPU-Hardware unterstützt werden und stattdessen im Shader-Programm kompiliert werden. Ein beliebtes Beispiel sind Scheitelpunktformate: Diese können in den Scheitelpunkt-Shader kompiliert werden, anstatt einen separaten Status auf dem Chip zu haben. Wenn Sie also Scheitelpunktformate verwenden, die Sie zuvor noch nicht mit einem bestimmten Scheitelpunkt-Shader verwendet haben, Möglicherweise zahlen Sie jetzt eine Menge CPU-Kosten, um den Shader zu patchen und das Shader-Programm auf die GPU zu kopieren. Darüber hinaus können der Treiber und der Shader-Compiler alle möglichen Aktionen ausführen, um die Ausführung des Shader-Programms zu optimieren. Dies kann bedeuten, dass Sie das Speicherlayout Ihrer Uniformen und Ressourcensteuerungsstrukturen so optimieren, dass sie gut in benachbarte Speicher- oder Shaderregister gepackt sind. Wenn Sie also den Shader wechseln, überprüft der Treiber möglicherweise alles, was Sie bereits an die Pipeline gebunden haben, und packt es in ein völlig anderes Format für den neuen Shader und kopiert es dann in den Befehlsstrom. Prinzip: Dies kann bedeuten, dass Sie das Speicherlayout Ihrer Uniformen und Ressourcensteuerungsstrukturen so optimieren, dass sie gut in benachbarte Speicher- oder Shaderregister gepackt sind. Wenn Sie also den Shader wechseln, überprüft der Treiber möglicherweise alles, was Sie bereits an die Pipeline gebunden haben, und packt es in ein völlig anderes Format für den neuen Shader und kopiert es dann in den Befehlsstrom. Prinzip: Dies kann bedeuten, dass Sie das Speicherlayout Ihrer Uniformen und Ressourcensteuerungsstrukturen so optimieren, dass sie gut in benachbarte Speicher- oder Shaderregister gepackt sind. Wenn Sie also den Shader wechseln, überprüft der Treiber möglicherweise alles, was Sie bereits an die Pipeline gebunden haben, und packt es in ein völlig anderes Format für den neuen Shader und kopiert es dann in den Befehlsstrom. Prinzip:Das Wechseln der Shader kann zu einem starken Mischen des CPU-Speichers führen.

Framebuffer-Änderungen sind wahrscheinlich am implementierungsabhängigsten, sind aber auf der GPU im Allgemeinen ziemlich teuer. Ihre GPU ist möglicherweise nicht in der Lage, mehrere Draw-Aufrufe an verschiedene Render-Ziele gleichzeitig zu verarbeiten. Daher muss möglicherweise die Pipeline zwischen diesen beiden Draw-Aufrufen unterbrochen werden. Möglicherweise müssen die Caches geleert werden, damit das Renderziel später gelesen werden kann. Möglicherweise muss die Arbeit, die während des Zeichnens verschoben wurde, aufgelöst werden. (Es ist sehr verbreitet, eine separate Datenstruktur zusammen mit Tiefenpuffern, MSAA-Renderzielen und vielem mehr zu akkumulieren. Dies muss möglicherweise abgeschlossen werden, wenn Sie von diesem Renderziel wegwechseln. Wenn Sie eine GPU verwenden, die auf Kacheln basiert Wie bei vielen mobilen GPUs muss möglicherweise ein ziemlich großer Teil der eigentlichen Shading-Arbeit gelöscht werden, wenn Sie von einem Frame-Puffer wegwechseln.) Prinzip:Das Ändern von Renderzielen ist auf der GPU teuer.

Ich bin sicher, dass das alles sehr verwirrend ist, und leider ist es schwierig, zu genau zu werden, da Details oft nicht öffentlich sind, aber ich hoffe, es ist ein halbwegs anständiger Überblick über einige der Dinge, die tatsächlich passieren, wenn Sie einen Staat anrufen Änderungsfunktion in Ihrer bevorzugten Grafik-API.

John Calsbeek
quelle