Gerüchteküche: Erzeugen StateObjects Garbage?

Es ist schnell, einfach und oft auch übersichtlicher, wenn man Objekte, die man benötigt erst dann instanziiert, wenn man sie benötigt, bzw. besser gesagt, kurz davor. Dies ist auch grundsätzlich nicht verkehrt, allerdings ist dies bei der Entwicklung von Spielen nicht unbedingt immer sinnvoll. Zum einen kostet das Erzeugen von Objekten natürlich Zeit und zum anderen bekommt der Garbage Collector (wenn er denn vorhanden ist, so wie es in C# und Java der Fall ist) unter Umständen etwas zu tun. Dies kann schnell dazu führen, daß soviel Zeit mit dem Aufräumen verbracht wird, daß das Spiel nicht mehr schnell genug ausgeführt wird.

In dieser neuen Kategorie meines Blog möchte ich in Zukunft Behauptungen untersuchen und auf ihre Richtigkeit beleuchten, ganz nach dem Vorbild der Myth Busters, allerdings mit weniger Knalleffekt. Diese Kategorie nenne ich Gerüchteküche.

Das erste Thema wurde mal wieder durch einen Forenbeitrag im XNA.mag angeregt und da ich an dieser Behauptung vermutlich nicht ganz unschuldig bin, möchte ich dies zum Anlass nehmen und dieses Mysterium aufklären.

Das Thema dieser Gerüchteküche:

StateObjects sollten vordefiniert werden, da das ständige Erzeugen in der Draw-Methode Garbage erzeugt.

Dies ist ein sehr interessantes Thema, insbesondere, da es sehr bequem ist, StateObjects in der Draw-Methode zu erzeugen, zu verwenden und dann wieder zu verwerfen. Insbesondere, da dies auch in einigen Beispielen aus dem App Hub gemacht wird.

Shawn Hargreaves hat ein klares Statement dazu:

Avoid creating new state objects every frame.

Die Frage ist nun, ob der Grund dafür die Erzeugung von Garbage ist? Dies können wir recht einfach prüfen, indem wir ein neues Windows Game 4.0 in Visual Studio erzeugen. In die Draw-Methode packen wir, nachdem wir einen SpriteFont mit dem Namen font erzeugt haben, den folgenden Code:

GraphicsDevice.DepthStencilState = DepthStencilState.None;

spriteBatch.Begin();

for (int i = 0; i < GC.MaxGeneration; i++)
{
spriteBatch.DrawString(font, String.Format("Generation {0} : {1}", i, GC.CollectionCount(i)), new Vector2(25, i * font.LineSpacing), Color.White);
}

spriteBatch.End();

DepthStencilState dss = new DepthStencilState();
dss.DepthBufferEnable = true;
GraphicsDevice.DepthStencilState = dss;

In Zeile 1 setzen wir ganz einfach einen der vordefinierten DepthStencilStates. Danach starten wir einen neuen SpriteBatch und fragen den Garbage Collector für jede Generation, wie oft er aufgeräumt hat und geben dies per DrawString auf dem Bildschirm aus. Danach erzeugen wir noch ab Zeile 12 ein selbstdefiniertes StateObject und verwenden dies auch gleich. Dieses wird dann beim nächsten Aufruf zurückgesetzt und schließlich neu erzeugt.

Dies sollte, wenn die Behauptung stimmt, Garbage erzeugen.

Wie wir sehen, sehen wir nichts 🙂 Es wird keine Garbage erzeugt, was die These um die es sich in diesem Beitrag dreht, eindeutig wiederlegt. Eindeutig wiederlegt? Nein, leider nicht wirklich. Ich muß zugeben, daß ich in der ersten Version dieses Artikels selbst darauf reingefallen bin. Es wird selbstverständlich Garbage erzeugt, da alle StateObjects von GraphicsResource abgeleitet sind und es sich dabei um einen Referenztyp handelt. Die Allokation von Referenztypen erzeugt selbstverständlich Garbage. Es dauert nur sehr lange, bis der Garbage Collector unter Windows etwas zu tun bekommt, bei mir waren dies mehrere Minuten, weshalb auf den ersten Blick alles im grünen Bereich war. Auf der XBox erfolgt dies etwa alle 10 Sekunden.

Zur Kontrolle können wir noch etwas mehr Garbage erzeugen und so schneller sicherstellen, daß unsere Messmethode korrekt funktioniert. Dazu fügen wir einfach folgende Zeile am Anfang der Draw-Methode ein:

SpriteBatch spriteBatch = new SpriteBatch(GraphicsDevice);

Treu nach dem Motte „Don’t try this at home“ sollte sowas NIEMALS gemacht werden. Wie wir bei einer erneuten Programmausführung sehen, wird jetzt Garbage erzeugt und zwar eine ganze Menge. Unsere Messmethode ist also richtig.

Darum schreibt The Shawn nun also, daß man State Objects nicht in jedem Frame erzeugen sollte. Sie sind zwar ein Leichtgewicht, trotzdem sind sie Referenz-Typen und verwenden sogar noch unmanaged Ressourcen, was bedeutet, daß wir sie mit Dispose explizit freigeben müssen. Es spricht nichts dagegen, die State Objects erst dann zu erzeugen, wenn man sie benötigt, ganz so, wie es in einigen Beispielen von Microsoft selbst auch gemacht wird. Dies ist ja auch recht übersichtlich, wie wir eingangs schon festgestellt hatten. Die Erzeugung eines State Objects kostet natürlich Zeit und da es sich ja um einen Referenztyp handelt wird auch der Garbage Collector Arbeit bekommen, wenn auch nicht viel. Diese Zeit kann man ganz einfach einsparen, in dem man sich selbst State Objects vordefiniert und diese verwendet. Da sich diese ja nicht mehr ändern, macht es keinen Sinn, diese in jedem Frame neu zu erzeugen.

Da es nur zu kleinen Performanceeinbrüchen durch seltene, zusätzliche Garbage Collections kommt, kann man durchaus in Beispielprogrammen so vorgehen, wie es Microsoft in seinen Beispielen vorlebt. Für richtige Spiele ist dies jedoch kein guter Weg, da es zwischenzeitlich immer wieder zu unerwünschten Garbage Collections kommen wird. Auf der XBox und auf dem Windows Phone treten diese deutlich häufiger auf.

Fazit

In dieser Gerüchteküche wurde eindeutig bewiesen, daß die Erzeugung von State Objects in jedem Frame minimal Garbage erzeugt und es wurde erklärt, daß es unter Umständen nicht weiter schlimm ist dies zu tun. Es wurde allerdings auch dargelegt, warum es trotzdem keinen Sinn macht und in produktivem und performanten Code vermieden werden sollte.

Advertisements

Veröffentlicht am 22.02.2011 in C#, Gerüchteküche, Grundlagen, Grundlagen, XNA, XNA 4.0 und mit , , , , getaggt. Setze ein Lesezeichen auf den Permalink. Ein Kommentar.

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: