Warum speichert Farseer 2.x Provisorien als Mitglieder und nicht auf dem Stapel? (.NETZ)

10

UPDATE: Diese Frage bezieht sich auf Farseer 2.x. Das neuere 3.x scheint dies nicht zu tun.

Ich verwende Farseer Physics Engine im Moment ziemlich häufig und habe festgestellt, dass es viele temporäre Werttypen als Mitglieder der Klasse zu speichern scheint und nicht wie erwartet auf dem Stapel.

Hier ist ein Beispiel aus der BodyKlasse:

private Vector2 _worldPositionTemp = Vector2.Zero;

private Matrix _bodyMatrixTemp = Matrix.Identity;
private Matrix _rotationMatrixTemp = Matrix.Identity;
private Matrix _translationMatrixTemp = Matrix.Identity;

public void GetBodyMatrix(out Matrix bodyMatrix)
{
    Matrix.CreateTranslation(position.X, position.Y, 0, out _translationMatrixTemp);
    Matrix.CreateRotationZ(rotation, out _rotationMatrixTemp);
    Matrix.Multiply(ref _rotationMatrixTemp, ref _translationMatrixTemp, out bodyMatrix);
}

public Vector2 GetWorldPosition(Vector2 localPosition)
{
    GetBodyMatrix(out _bodyMatrixTemp);
    Vector2.Transform(ref localPosition, ref _bodyMatrixTemp, out _worldPositionTemp);
    return _worldPositionTemp;
}

Es sieht so aus, als wäre es eine Leistungsoptimierung von Hand. Aber ich sehe nicht ein, wie dies möglicherweise zur Leistung beitragen könnte? (Wenn überhaupt, würde es meiner Meinung nach weh tun, wenn Objekte viel größer gemacht würden).

Andrew Russell
quelle

Antworten:

6

Obwohl in .NET Werttypen auf dem Stapel gespeichert sind, was zu minimalen Zuordnungskosten führt, werden die Initialisierungskosten jedoch nicht eliminiert.

In diesem Fall haben wir eine Reihe von Funktionen, die eine oder zwei temporäre Matrizen verwenden, was zur Initialisierung von 16-32 Floats pro Aufruf führen würde. Dies mag zwar unbedeutend erscheinen, aber wenn die Methoden häufig genug verwendet werden (z. B. Tausende und Abertausende Male pro Frame), kann der Gesamtaufwand einen bedeutenden Einfluss haben. Wenn eine solche Technik systematisch für alle diese Methoden verwendet wird, kann der eliminierte Overhead beträchtlich sein.

Während die Verwendung einer solchen Technik die Möglichkeit beseitigt, Gewindesicherheit auf Objektebene bereitzustellen, ist es im Allgemeinen unklug, diese Garantie auf einer solchen granularen Ebene bereitzustellen.

Jason Kozak
quelle
Bist du dir da sicher? Ein Gedanke, den ich hatte, war, dass es vermeiden könnte, Konstruktoren aufzurufen. Für Werttypen müssen Sie jedoch keinen Konstruktor aufrufen - einschließlich des standardmäßigen parameterlosen Konstruktors -, wenn Sie alle Elemente festlegen (oder als outParameter übergeben) möchten. Ich bin mir ziemlich sicher, dass der ganze Sinn dieser Regel darin besteht, dass der Compiler das Nullsetzen dieses Speichers überspringen kann - richtig? (Ist es wirklich so langsam, den Stapelzeiger zu bewegen?)
Andrew Russell
Überraschend, nein? Wenn Sie die generierte IL überprüfen, werden die temporären Matrizen leider initialisiert. Einige schnelle Tests zeigen, dass die temporäre Version für Mitglieder ~ 10-15% schneller ist.
Jason Kozak
1
Ich bin fassungslos . In "Understanding XNA Framework Performance" (GDC2008) sagt Shawn Hargreaves über Strukturen: "[die JIT] wird normalerweise herausfinden: 'In der nächsten Zeile setzt er sofort alle drei Felder [des Vector3], also brauche ich nicht einmal um es auf Null zu initialisieren '" . Woher kommen meine Informationen? Aber wenn er jetzt wieder zuhört, sagt er nur "normalerweise". Der nächste Punkt in der Präsentation ist, dass sich die JIT mit dem angehängten Debugger anders verhält, was sich auf die Leistung auswirkt (wie haben Sie getestet?). Außerdem: Er spricht hier über die JIT, also bleibt die IL vielleicht "nett" (Überprüfbarkeit?).
Andrew Russell
Die IL wurde über Reflector inspiziert und die Tests wurden außerhalb der in Release integrierten IDE ausgeführt (unter Windows habe ich keine CC-Mitgliedschaft mehr, gegen die ich testen kann)
Jason Kozak
1
Auf dieser Grundlage frage ich mich, ob es besser wäre (und wie viel besser es wäre), diese Mitglieder-Provisorien zu erstellen static(und / oder sie aggressiver wiederzuverwenden). So wie es zum Beispiel ist, hat die BodyKlasse in Farseer 73 Floats mit "unnötigen" Mitgliedern.
Andrew Russell
-1

Gute Frage. Ich bin ein ziemlich scharfer C # /. NET-Typ und ein bisschen eine Performance-Nuss, und das scheint mir eine ziemlich seltsame Designentscheidung zu sein. Das erste, was mir auffällt, ist, dass dieser Code in keiner Weise threadsicher ist. Ich weiß nicht, ob dies ein Problem in einem Physiksystem ist, aber das Speichern temporärer Daten außerhalb des Anwendungsbereichs einer Methode ist oft ein Rezept für eine Katastrophe.

Wenn ich regelmäßig auf diese Art von Code in einem Framework eines Drittanbieters stoßen würde, würde ich wahrscheinlich versuchen, ein anderes Framework zu finden.

Mike Strobel
quelle
3
Beantwortet die Frage nicht wirklich.
Brian Ortiz
Ja, das Beste, was ich tun kann, ist zu bestätigen, dass er nicht verrückt ist, und es scheint keinen wirklichen Vorteil zu geben, wenn es so codiert wird. Der einzige Grund für die wahre Absicht ist, den Mann zu fragen, der den Code geschrieben hat :).
Mike Strobel
Danke, Mike. Ich fange an zu vermuten, dass der ursprüngliche Entwickler der Verrückte ist, nicht ich. Aber es hilft immer zu überprüfen;)
Andrew Russell
Die Thread-Sicherheit kann manchmal eine kostspielige Garantie sein, insbesondere wenn eine FP-rechenintensive Bibliothek für eine Plattform geschrieben wird, die keine SIMD-Anweisungen verwendet.
Jason Kozak
-1

Der GC auf dem 360 führt im Grunde nur GEN 2-Sammlungen aus, die teuer sind. Daher führen temporäre Variablen, die in jedem Frame erstellt und entfernt werden (wie temporäre Objekte), dazu, dass eine ganze Sammlung ausgeführt wird, was die Leistung sehr schnell beeinträchtigt.

Ich vermute, sie haben es auf diese Weise getan, um dieses Objekt wiederzuverwenden und nicht sammeln zu lassen.

Steven Evers
quelle
1
Dies kam mir auch in den Sinn, aber die temporären Mitglieder scheinen Werttypen zu sein, sodass sie ohnehin nicht auf dem verwalteten Heap zugewiesen werden.
Mike Strobel
2
Richtig, aber nur, wenn sie Klassenmitglieder sind. Wenn es sich um Einheimische handelt (mit Methodenbereich), werden sie auf dem Stapel zugewiesen. Die Frage ist, warum sie diesen Weg nicht einfach gegangen sind.
Mike Strobel
1
@Blair - Laut MSDN ( msdn.microsoft.com/en-us/library/bb203912.aspx ) verwendet die Xbox360 das .NET Compact Framework. Es scheint, dass der Unterschied in der GC damit zusammenhängt, also würde ich das für weitere Forschungen in dieser Angelegenheit weiterverfolgen.
Logan Kincaid
1
@Blair: @Logan Kincaid ist richtig. Der Garbage Collector des CF verhält sich anders als der reguläre Framework. In XNA Game Studio 3.0 Unleashed gibt es einen guten Vortrag zu diesem Thema - dieses Buch wird jedoch mit der Veröffentlichung von 4.0 bald veraltet sein.
Steven Evers
1
@Blair: Aufgrund der begrenzten Speicherumgebung des 360 (und der meisten Geräte, auf die über die CF abgezielt wird) wird ein Mark & ​​Sweep-GC verwendet. Infolgedessen lösen viele kleine Zuordnungen eine Sammlung aus, und die Erfassungszeit ist relativ zur Anzahl der Referenzen. Viele Details hier: download.microsoft.com/.../Mobility/…
Jason Kozak