Wie werden viele Aktualisierungsobjekte in C # effizient gehandhabt?

8

Ich entwickle ein 2D-Overhead-Shooter-Spiel mit C # und XNA. Ich habe eine Klasse, die ich "bullet" nennen werde und viele dieser Instanzen jeden Bruchteil einer Sekunde aktualisieren muss.

Meine erste Möglichkeit war, eine generische Liste mit Aufzählungszeichen zu erstellen und einfach neue Aufzählungszeichen zu entfernen und hinzuzufügen. Aber dadurch tritt der GC oft an und mein Spiel hatte eine periodische ruckartige Verzögerung. (Viel Code ausgeschnitten, aber wollte nur ein einfaches Snippet zeigen)

%Vor%

Mein zweiter und aktueller Versuch ist es, einen separaten generischen Stapel von Geschossen zu haben, an den ich schiebe, wenn ich mit einem Geschoss fertig bin, und ein Geschoss platzen lasse, wenn ich ein neues benötige, wenn irgendetwas im Stapel ist. Wenn sich im Stapel nichts befindet, füge ich der Liste ein neues Aufzählungszeichen hinzu. Es scheint zu sein, die ruckartige Verzögerung zu reduzieren, aber manchmal gibt es immer noch etwas ruckartige Verzögerung (obwohl ich nicht weiß, ob es verwandt ist).

%Vor%

Also, ich weiß, dass vorzeitige Optimierung die Wurzel allen Übels ist, aber das war eine sehr bemerkenswerte Ineffizienz, die ich früh auffangen konnte (und das war, bevor man sich sogar darum sorgen musste, dass feindliche Kugeln den Bildschirm füllten). Meine Fragen sind also: Werden unbenutzte Objekte auf einen Stapel geschoben, wird die Garbage Collection aufgerufen? Werden die Referenzen am Leben erhalten oder werden Objekte noch zerstört? Gibt es eine bessere Möglichkeit, mit der Aktualisierung vieler verschiedener Objekte umzugehen? Zum Beispiel, werde ich zu extravagant? Wäre es in Ordnung, einfach die Liste zu durchlaufen und auf diese Weise ein ungenutztes Geschoss zu finden?

    
Bob 25.02.2010, 16:56
quelle

6 Antworten

11

Es gibt viele Probleme hier, und es ist schwierig zu sagen.

Erstens, ist bullet eine Struktur oder eine Klasse? Wenn bullet eine Klasse ist, jedes Mal, wenn Sie ein Konstrukt erstellen, entfernen Sie es aus der Liste (lassen Sie es außerhalb des Bereichs oder setzen Sie es auf null), fügen Sie etwas hinzu, das der GC zum Sammeln benötigt.

Wenn Sie viele davon erstellen und jedes Bild aktualisieren, sollten Sie die Verwendung von List<bullet> mit bullet als Struktur und die Vorabzuweisung der Liste (generieren Sie sie mit eine Größe, die groß genug ist, um alle Ihre Kugeln aufzunehmen, sodass sie nicht neu erstellt wird, wenn Sie List.Add aufrufen. Dies wird dramatisch mit dem GC-Druck helfen.

Auch, nur weil ich schimpfen muss:

  

Also, ich weiß, vorzeitige Optimierung ist die Wurzel allen Übels, aber das war sehr auffällige Ineffizienz

Niemals Angst haben, eine Routine zu optimieren, die Probleme verursacht . Wenn Sie ein Leistungsproblem sehen (z. B. Ihre Verzögerungen), ist dies keine vorzeitige Optimierung mehr. Ja, Sie möchten nicht jede Codezeile optimieren, aber müssen den Code optimieren , insbesondere wenn Sie ein echtes Leistungsproblem sehen. Es zu optimieren, sobald Sie sehen, dass es ein Problem ist, ist viel einfacher als zu versuchen, es später zu optimieren, da alle erforderlichen Designänderungen viel einfacher implementiert werden können, bevor Sie viel anderen Code hinzugefügt haben, der Ihre bullet -Klasse verwendet / p>     

Reed Copsey 25.02.2010, 17:02
quelle
4

Sie finden das Fliegengewicht-Designmuster hilfreich. Es muss nur ein Bullet-Objekt vorhanden sein, aber mehrere Fliehgewichte können unterschiedliche Positionen und Geschwindigkeiten dafür angeben. Die Fliehgewichte können in einem vorher zugewiesenen Array (z. B. 100) gespeichert und als aktiv markiert werden oder nicht.

Das sollte die Müllsammlung vollständig eliminieren und den Platz für die Verfolgung der formbaren Eigenschaften jedes Geschosses reduzieren.

    
Steven A. Lowe 25.02.2010 17:18
quelle
2

Ich gebe zu, dass ich in dieser Hinsicht keine Erfahrung habe, aber ich würde in Betracht ziehen, ein traditionelles Array zu verwenden. Initialisieren Sie das Array auf eine Größe, die größer ist als Sie benötigen. Dies wäre die theoretisch maximale Anzahl von Aufzählungszeichen, sagen wir 100. Ordnen Sie dann beginnend mit 0 die Aufzählungszeichen am Anfang des Arrays zu und belassen das letzte Element als Null. Wenn Sie also vier aktive Kugeln hätten, würde Ihr Array wie folgt aussehen:

0 B 1 B 2 B 3 B 4 null ... 99 null

Der Vorteil besteht darin, dass das Array immer zugewiesen wird und Sie sich daher nicht mit dem Aufwand einer komplexeren Datenstruktur befassen müssen. Das ist eigentlich ziemlich ähnlich wie Strings funktionieren, da sie eigentlich char [] mit einem Null-Terminator sind.

Könnte einen Versuch wert sein. Ein Nachteil, Sie müssen einige manuelle Manipulationen vornehmen, wenn Sie eine Kugel entfernen, wahrscheinlich bewegen Sie alles nach dieser Kugel einen Schlitz. Aber du bewegst gerade Zeiger an diesem Punkt, also denke ich nicht, dass es eine hohe Strafe wie das Zuweisen von Speicher oder ein GC hätte.

    
Andrew Dunaway 25.02.2010 17:03
quelle
2

Sie haben Recht, wenn Sie davon ausgehen, dass die unbenutzten Aufzählungszeichen in einem Stapel verhindern, dass sie als Sammlungsabfälle angezeigt werden.

Was haben Sie als Ursache für die Verzögerung probiert? Nur um herauszufinden, wo das Problem liegt.

    
TJMonk15 25.02.2010 17:31
quelle
2

Ihre stackbasierte Lösung ist ziemlich nah an einer Klasse, die ich geschrieben habe, um diese Art von Ressourcenpooling generisch zu machen:
Ссылка

Sie haben erwähnt, dass dadurch das Problem größtenteils verschwindet, aber es kommt immer noch hier und da vor. Was passiert, ist, dass mit dieser stapel / queuebasierten Pooling-Methode das System einen Stabilitätspunkt erreicht, sobald Sie nicht mehr neue Objekte anfordern, als der Pool liefern kann. Wenn die Anforderungen jedoch höher als die vorherige maximale Anzahl der angeforderten Elemente sind, müssen Sie eine neue Instanz erstellen, um die Anfrage zu bearbeiten (wodurch GC von Zeit zu Zeit aufgerufen wird).

Eine Möglichkeit, dies zu umgehen, besteht darin, so viele Instanzen zu durchlaufen und vorzubelegen, wie Sie es für erforderlich halten. Auf diese Weise haben Sie keine neuen Zuordnungen (zumindest von den gepoolten Objekten), und der GC wird nicht ausgelöst: -)

    
Joel Martinez 25.02.2010 21:07
quelle
1
Die

-Liste hat eine eingebaute Kapazität, um die Zuweisung für jedes Hinzufügen / Entfernen zu verhindern. Sobald Sie die Kapazität überschreiten, fügt es mehr hinzu (ich denke, dass ich jedes Mal verdoppelt). Das Problem kann mehr auf Entfernen als Hinzufügen sein. Hinzufügen wird nur an der ersten offenen Stelle angezeigt, die nach Größe verfolgt wird. Um sie zu entfernen, muss die Liste komprimiert werden, um den jetzt leeren Slot zu füllen. Wenn Sie immer für die Vorderseite der Liste entfernen, muss jedes Element nach unten rutschen.

Ein Stapel verwendet immer noch ein Array als internen Speichermechanismus. Sie sind also immer noch an die Eigenschaften zum Hinzufügen / Entfernen eines Arrays gebunden.

Damit das Array funktioniert, müssen Sie alle Aufzählungszeichen mit jeweils einer Active-Eigenschaft erstellen. Wenn Sie eine neue benötigen, füllen Sie das Attribut "Aktiv" auf "Wahr" und legen Sie alle neuen Eigenschaften fest. Wenn Sie fertig sind, setzen Sie das Aktiv-Flag auf false.

Wenn Sie versuchen wollten, die Liste (die sehr groß sein könnte, je nachdem, was Sie zulassen möchten) für jedes Repaint zu iterieren, könnten Sie versuchen, eine doppelt verkettete Liste innerhalb des Arrays zu implementieren. Wenn ein neues Geschoss benötigt wird, wurde das Array nach dem ersten verfügbaren freien Eintrag gefragt. Gehen Sie zum letzten aktiven Bullet (einer Variablen) und fügen Sie die neue Position des Bullet-Arrays in die nächste aktive Bullet-Eigenschaft ein. Wenn es an der Zeit ist, es zu entfernen, gehen Sie zum vorherigen Punkt und ändern Sie seine aktive bullet -Eigenschaft in den entfernten nächsten aktiv.

%Vor%

Auf diese Weise haben Sie ein vorgefertigtes Array mit allen benötigten Kugeln, aber die Farbe trifft nur auf die Kugeln, die unabhängig von ihrer Position innerhalb des Arrays aktiv sind. Sie müssen nur eine Verbindung zum ersten aktiven Bullet halten, um den Paint zu starten, und zum letzten aktiven Bullet, um zu wissen, wo die Liste von neuem beginnt.

Jetzt machen Sie sich nur Sorgen um den Speicher, um die gesamte Liste zu halten, anstatt wenn der GC aufräumt.

    
JDMX 25.02.2010 17:53
quelle

Tags und Links