Optimierungen

Immer wieder kommen Themen auf, bei denen die Frage gestellt wird, wie man am besten optimiert und wie man etwas beschleunigen kann. Dies ist nicht immer einfach zu erklären, bzw. auch nicht unbedingt immer eindeutig, da ohne eine genaue Kenntnis des zu Optimierenden dies in ein Rätselraten ausartet.

Um dem Anfänger und auch Fortgeschrittenen ein paar Hinweise und Tipps zu geben, habe ich mich dazu entschlossen, diesen Artikel zu verfassen. Dieser enthält zum einen ein paar grundlegende Dinge und Regeln, aber auch ein paar konkrete, wenn auch allgemeine Hinweise auf möglich Optimierungen.

Die erste Regel der Optimierung ist:

Die wichtigste Optimierung ist immer, etwas nicht zu tun.

Klingt interessant und auch logisch und das ist es auch. Alles was in einem Programm ausgeführt wird, kostet Rechenzeit und/oder Ressourcen und ist damit geeignet mein Programm zu verlangsamen.

Zeichne ich 1000 Sprites, dann ist dies natürlich langsamer, als würde ich nur 100 Sprites zeichnen. Wenn ich es also schaffen kann Sprites wegzulassen (man nennt diesen Vorgang übrigens Culling), dann wird es schneller.

Ein weiteres Beispiel sind Schleifen. Oft wird der Fehler gemacht, daß innerhalb von Schleifen Werte berechnet werden, die bei jedem Schleifendurchlauf den gleichen, konstanten Wert haben. Eine sehr einfache Optimierung ist nun, diesen Wert einfach vor Schleifeneintritt zu berechnen. Man spart sich also die Berechnung innerhalb der Schleife und hat so dem Prozessor eine Menge Arbeit erspart, die er selbstverständlich mit einer höheren Ausführungsgeschwindigkeit dankt.

Die zweite Regel der Optimierung ist:

Optimiere nur, wenn du optimieren musst.

Es macht keinen Sinn, alles bis zum letzten Prozessortakt optimieren zu wollen. Dies hat mehrere Gründe:

  1. Eine Optimierung, die nur der Optimierung Willen vorgenommen wird, führt oft dazu, daß der Code schwerer wartbar und weniger lesbar wird.
  2. Jede Optimierung kostet Zeit, die an anderer Stelle besser aufgehoben sein kann.
  3. Optimierungen müssen immer im Gesamtkontext getestet werden. Eine Methode zu optimieren, die nur 1% der Gesamtlaufzeit eines Programmes ausmacht, wird nicht viel Ersparnis bringen. Auch wenn man diese Methode zu 75% optimieren kann, dann wird sich die Gesamtlaufzeit nur um 0.75% verbessern.

Diese Regel ist natürlich etwas schwammig und mit zunehmender Erfahrung hat der Entwickler immer mehr ein Gefühl dafür, wie diese Regel anzuwenden ist. Man gewöhnt sich also praktisch einen guten Stil an und verinnerlicht Algorithmen, die dann sofort angewendet werden.

Anfänger (aber auch Fortgeschrittene) machen offen den Fehler und versuchen „so viele Frames wie möglich“ zu rendern. Mehr als 60 (je nach Genre reichen auch 30) sind aber oft gar nicht notwendig. Oft wird versucht, auf Vorrat zu optimieren, damit im Verlauf der weiteren Entwicklung noch genug Luft vorhanden ist für neue Effekte, mehr Modelle etc. Dies macht auch keinen Sinn. Wenn es zu langsam wird, dann ist ein viel besserer Zeitpunkt dies zu tun.

Die dritte Regel der Optimierung ist:

Optimiere nur das, was du kennst.

Diese Regel hängt stark mit der vorherigen Regel zusammen. Es macht keinen Sinn, etwas zu optimieren, von dem du meinst, daß es optimiert werden muss. Du musst analysieren, wo das Problem liegt und das geht am besten mit einem Profiler wie NProf. Dieser führt das Programm aus und ermittelt welche Programmteile wieviel Rechenzeit beanspruchen. Den größten Effekt erzielst du natürlich immer dann, wenn du Programmteile optimierst, die am meisten Rechenzeit beanspruchen.

Auch ist es wichtig zu ermitteln, wo die Geschwindigkeit überhaupt verloren geht. CPU und GPU (Grafikkartenprozessor) arbeiten parallel. Wenn es ruckelt, dann muss nicht unbedingt die Grafikkarte zu langsam sein. Es kann auch sein, daß die CPU die GPU ausbremst. Genauso kann umgekehrt ein Flaschenhals vorliegen. Eine gute Möglichkeit dies zu ermitteln wird von Shawn Hargreaves im Blogeintrag How to tell if you are CPU or GPU bound beschrieben.

Ein sehr gutes Tool zum Debuggen der GPU ist übrigens PIX. Dieses ist im DirectX SDK enthalten und ermöglicht exaktes Debuggen der Grafikausgabe.

Nach diesen eher allgemeinen Tipps nun ein paar konkrete, aber allgemein gefasste Tipps zu Optimierungen. Einige davon sind etwas näher ausgeführt und beschrieben, einige enthalten sogar Links zu eigenen Artikeln, die weiterführende Informationen beinhalten.

Über die Kommentarfunktion können natürlich gerne Wünsche platziert werden. Vielleicht werde ich dann ja zum einen oder anderen Punkt noch mehr Detailinformationen liefern.

Hier nun aber die Liste:

  • Variablen und Objekte nicht innerhalb von Schleifen deklarieren (z.B. State Objects)
  • Möglichst wenig Verwendung von new, insbesondere in Schleifen und in den Methoden Update und Draw
  • Möglichst viel vorberechnen. (Rectangles für SpriteBatch.Draw z.B. nicht jedes mal neu berechnen, sondern nur dann, wenn sich diese ändern)
  • Kleine, zeitkritische Methoden manuell inlinen.
  • ref und out Varianten der Methoden von Vector2, Vector3, Matrix etc. verwenden.
  • Möglichst wenig RenderState-Changes (Filter ein/ausschalten, FillMode ändern, DepthBuffer ändern, RenderTargets setzen und auflösen etc.)
  • Möglichst wenig DrawCalls. (Model.Draw, SpriteBatch.Draw, DrawIndexedPrimitives etc.)
  • Nur sichtbares zeichnen (dies nennt man Culling. Aber nicht zuviel Zeit für Culling verschwenden. Wenn das Culling länger dauert, als die Ersparnis, dann macht das keinen Sinn).
  • Von vorne nach hinten sortieren um Füllrate zu sparen und das Early Z-Culling auszunutzen.
  • Möglichst wenig Effekte verwenden.
  • Sparsam mit Shadern sein.
  • Overdraw vermeiden (Riesige Sprites mit vielen transparenten Flächen so eng wie möglich schneiden. Auch transparente Bereiche kosten Füllrate).
  • Möglichst Power-Of-Two Texturen verwenden.
  • Texturen DXT-komprimieren.
  • Tiles nicht zu extrem unterteilen. Wenn z.B. ein Baum aus 6 „kleinen“ Tiles besteht und immer gleich gerendert wird, dann ist ein größeres Tile viel schneller.
Advertisements

Veröffentlicht am 11.01.2011, in C#, Grundlagen, Grundlagen, Optimierung, XNA. Setze ein Lesezeichen auf den Permalink. 3 Kommentare.

  1. Kleine Frage: Warum sollte man möglichst Power-Of-Two Texturen verwenden?

    • Zum einen weil einige GPU’s (heutzutage insbesondere auf Mobile-Geräten) nur PoT-Texturen darstellen können. Und damit kommen wir zum anderen: Durch die Verwendung von PoT-Texturen können einige Dinge „optimiert“ werden. Beispielsweise muss beim Mipmapping einfach nur die Breite und Höhe halbiert werden. Auch das Filtering hat weniger „Sonderfälle“ und wenn diese nicht beachtet werden müssen, so kann ein wenig mehr Speed herausgeholt werden. Auf dem PC ist das zwar nicht die Welt, aber da dies ja recht einfach sicherzustellen ist, kann man diese einfache Optimierung halt meist einfach so machen.

  2. Danke für die Info.
    Du machst das echt gut. Freue mich schon auf weitere Artikel.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: