Welche negativen Auswirkungen hat die Erweiterung von Klassen in ActionScript 3?

8

In meiner Game Engine verwende ich Box2D für die Physik. Die Benennungskonventionen von Box2D und die schlechte Kommentierung ruinieren den konsistenten und gut dokumentierten Rest meiner Engine, der ein wenig frustrierend ist und schlecht präsentiert, wenn Sie ihn verwenden.

Ich habe darüber nachgedacht, Wrapper-Klassen für Box2D zu erstellen. Das heißt, Klassen, die jedes der üblichen Box2D-Objekte erweitern und deren Funktionen neu geschrieben werden, um den Namenskonventionen des Rests meiner Engine zu folgen und sie deutlicher und konsistent zu kommentieren. Ich habe sogar darüber nachgedacht, einige der Klassen zu erstellen und einige kleine Teile hinzuzufügen (wie Getter für pixelbasierte Messungen in der Klasse b2Vec2 ).

Das ist in Ordnung, aber ich bin mir nicht 100% sicher, welche negativen Auswirkungen das haben würde und in welchem ​​Ausmaß sich diese auf meine Anwendungen und Spiele auswirken würden. Ich bin mir nicht sicher, ob der Compiler einige meiner Bedenken in gewissem Maße lindert oder ob ich beim Hinzufügen etwas unnötiger Klassen aus Gründen der Lesbarkeit und Konsistenz rücksichtsvoll sein muss.

Ich habe einige Vermutungen:

  • Mehr Speicherverbrauch, um der zusätzlichen Klassenstruktur Rechnung zu tragen.
  • Auswirkungen auf die Leistung beim Erstellen neuer Objekte aufgrund der Initialisierung einer zusätzlichen Mitgliederebene?

Ich frage speziell nach Laufzeitauswirkungen.

    
Marty 13.09.2013, 00:39
quelle

5 Antworten

6

Dies ist ein ziemlich häufiges Problem, wenn es darum geht, Bibliotheken von Drittanbietern zu integrieren, insbesondere Bibliotheken, die Ports sind (wie Box2DAS3), wobei sie die Kodierungs- und Benennungskonventionen der übergeordneten Sprache beibehalten und nicht vollständig in die Zielsprache integrieren ( Fall in Punkt: Box2DAS3 mit getFoo() und setFoo() anstelle von .foo Getter / Setter).

Um Ihre Frage schnell zu beantworten, nein, es wird keine nennenswerten Auswirkungen auf die Leistung geben, wenn Wrapper-Klassen erstellt werden. nicht mehr, als Sie in der Klassenhierarchie in Ihrem eigenen Projekt sehen. Sicher, wenn Sie eine Schleife von 5 Millionen Iterationen Zeit, Sie sehen möglicherweise eine Millisekunde oder zwei der Unterschied, aber bei normaler Verwendung, werden Sie es nicht bemerken.

"Mehr Speicherverbrauch, um der zusätzlichen Klassenstruktur Rechnung zu tragen." Wie bei jeder anderen Sprache, die eine Klassenvererbung hat, wird im Hintergrund eine Vtable verwendet, so dass Sie eine kleine Steigerung des Arbeitsspeichers / Perf haben, aber das ist vernachlässigbar.

"Leistungseinbußen beim Erstellen neuer Objekte aufgrund der Initialisierung einer zusätzlichen Mitgliederebene?" Nicht mehr als eine normale Instanziierung, also sollten Sie sich keine Sorgen machen, es sei denn, Sie erstellen eine große Menge an Objekten.

Im Hinblick auf die Leistung sollten Sie generell kein Problem haben (bevorzugen Sie Lesbarkeit und Benutzerfreundlichkeit gegenüber der Leistung, es sei denn, Sie haben tatsächlich ein Problem damit), aber ich würde es eher als ein architektonisches Problem betrachten und in diesem Sinne was würde eine negative Auswirkung der Erweiterung / Änderung von Klassen einer externen Bibliothek im Allgemeinen in drei Bereiche fallen, je nachdem, was Sie tun möchten:

  • Ändern Sie die Bibliothek
  • Erweitern Sie die Klassen
  • Komposition mit eigenen Klassen

Ändern Sie die Bibliothek

Da Box2DAS3 Open Source ist, gibt es nichts, was Sie davon abhält, alle Klassen- und Funktionsnamen nach Herzenslust zu springen und zu refactorisieren. Ich habe ernsthaft darüber nachgedacht, das manchmal zu tun.

Vorteile:

  • Sie können ändern, was Sie wollen - Funktionen, Klassen, Sie nennen es
  • Sie können fehlende Teile hinzufügen, die Sie benötigen (z. B. Pixel-Meter-Konvertierungshelfer)
  • Sie können alle möglichen Leistungsprobleme beheben (ich habe einige Dinge bemerkt, die besser und schneller gemacht werden könnten, wenn sie auf "AS3" Weise gemacht wurden)

Nachteile:

  • Wenn Sie Ihre Version auf dem neuesten Stand halten möchten, müssen Sie alle Aktualisierungen und Änderungen manuell zusammenführen / konvertieren. Für populäre Bibliotheken oder solche, die sich stark verändern, kann dies ein großer Schmerz
  • sein
  • Es ist sehr zeitaufwendig - abgesehen von Änderungen brauchen Sie ein gutes Verständnis darüber, was vor sich geht, damit Sie Änderungen vornehmen können, ohne die Funktionalität zu beeinträchtigen
  • Wenn mehrere Personen damit arbeiten, können sie sich nicht so sehr auf externe Dokumentation / Beispiele verlassen, da sich die Interna geändert haben könnten

Erweitern Sie die Klassen

Hier erstellen Sie einfach Ihre eigenen Wrapper-Klassen, die die Basis-Box2D-Klassen erweitern. Sie können Eigenschaften und Funktionen hinzufügen, wie Sie möchten, einschließlich der Implementierung eines eigenen Namensschemas, das in die Basisklasse übersetzt (z. B. MyBox2DClass.foo() könnte einfach ein Wrapper für Box2DClass.bar() sein)

Vorteile:

  • Sie implementieren nur die Klassen, die Sie benötigen, und machen nur die notwendigen Änderungen
  • Ihre Wrapper-Klassen können weiterhin in der Box2D-Basis-Engine verwendet werden - d. h. Sie können ein MyBox2DClass -Objekt an eine interne Methode übergeben, die ein Box2DClass übernimmt und Sie wissen, dass es funktioniert
  • Es ist die geringste Menge an Arbeit, von allen drei Methoden

Nachteile:

  • Auch hier gilt: Wenn Sie Ihre Version auf dem neuesten Stand halten möchten, müssen Sie überprüfen, ob Änderungen Ihre Klassen beeinträchtigen. Normalerweise kein großes Problem, obwohl
  • Kann Verwirrung in die Klasse bringen, wenn Sie eigene Funktionen erstellen, die ihr Box2D-Äquivalent aufrufen (zB "Soll ich setPos() oder SetPosition() verwenden?). Auch wenn Sie alleine arbeiten, wenn Sie kommen zurück zu deiner Klasse in 6 Monaten, wirst du
  • vergessen haben
  • Ihren Klassen mangelt es an Kohärenz und Konsistenz (z. B. bei einigen Funktionen, die Ihre Benennungsmethode verwenden ( setPos() ), bei anderen dagegen Box2D ( SetPosition() ))
  • Sie sind mit Box2D festgefahren; Sie können Physik-Engines nicht ohne viel Entwickler ändern, abhängig davon, wie Ihre Klassen im gesamten Projekt verwendet werden. Dies ist möglicherweise keine so große Sache, wenn Sie nicht
  • umstellen möchten

Komposition mit eigenen Klassen

Sie erstellen eigene Klassen, die intern eine Box2D-Eigenschaft enthalten (z. B. MyPhysicalClass hat eine Eigenschaft b2Body ). Sie können Ihre eigene Schnittstelle nach Belieben implementieren und nur das, was notwendig ist.

Vorteile:

  • Ihre Klassen sind sauberer und passen gut zu Ihrem Motor.Nur Funktionen, für die Sie sich interessieren, sind verfügbar
  • Sie sind nicht an die Box2D Engine gebunden; Wenn Sie beispielsweise zu Nape wechseln möchten, müssen Sie nur Ihre benutzerdefinierten Klassen ändern. Der Rest Ihrer Engine und Spiele sind unbewusst. Andere Entwickler müssen auch nicht die Box2D-Engine lernen, um sie verwenden zu können
  • Während Sie dort sind, können Sie sogar mehrere Engines implementieren und zwischen ihnen mit einem Toggle oder Schnittstellen wechseln. Auch hier ist der Rest Ihrer Engine und Spiele nicht bekannt
  • Funktioniert gut mit komponentenbasierten Engines - z. Sie können eine Box2DComponent haben, die eine b2Body -Eigenschaft
  • enthält

Nachteile:

  • Mehr Arbeit als nur das Erweitern der Klassen, da Sie im Wesentlichen eine Zwischenschicht zwischen Ihrer Engine und Box2D erstellen. Außerhalb Ihrer benutzerdefinierten Klassen sollte idealerweise kein Verweis auf Box2D vorhanden sein. Die Menge an Arbeit hängt davon ab, was Sie in Ihrer Klasse brauchen
  • Zusätzliche Ebene der Indirektion; Normalerweise sollte dies kein Problem sein, da Box2D die Eigenschaften von Box2D direkt verwendet, aber wenn Ihre Engine Ihre Funktionen häufig aufruft, ist dies ein zusätzlicher Schritt auf dem Weg, leistungsmäßig

Von den dreien bevorzuge ich die Komposition, da sie die größte Flexibilität bietet und die Modulnatur Ihrer Engine intakt hält, dh Sie haben Ihre Kern-Engine-Klassen und Sie erweitern -Funktionalität mit externen Bibliotheken. Die Tatsache, dass Sie Bibliotheken mit minimalem Aufwand wechseln können, ist ebenfalls ein großes Plus. Dies ist die Technik, die ich in meiner eigenen Engine verwendet habe, und ich habe sie auch auf andere Arten von Bibliotheken ausgedehnt - z. Ads - Ich habe meine Engine-Ad-Klasse, die mit Mochi, Kongregate, etc. nach Bedarf integriert werden kann - der Rest meines Spiels ist mir egal, was ich benutze, wodurch ich meinen Coding-Stil und Konsistenz während der gesamten Engine beibehalten kann immer noch flexibel und modular.

----- Update 20/9/2013 -----

Große Aktualisierungszeit! Also ging ich zurück, um etwas über Größe und Geschwindigkeit zu testen. Die Klasse, die ich verwendet habe, ist zu groß, um sie hier einzufügen. Sie können sie also unter Ссылка

herunterladen

Darin teste ich eine Anzahl von Klassen:

  • Eine Empty Instanz; Eine Klasse, die nur Object erweitert und eine leere getPostion() -Funktion implementiert. Dies wird unser Maßstab sein
  • Eine b2Body Instanz
  • A Box2DExtends Instanz; Eine Klasse, die b2Body erweitert und eine Funktion getPosition() implementiert, die nur GetPosition() (die b2Body -Funktion)
  • zurückgibt
  • A Box2DExtendsOverrides Instanz; Eine Klasse, die b2Body erweitert und die Funktion GetPosition() überschreibt (sie gibt einfach super.GetPosition() zurück)
  • A Box2DComposition Instanz; Eine Klasse mit einer b2Body -Eigenschaft und einer getPosition() -Funktion, die die b2Body's GetPosition() zurückgibt.
  • A Box2DExtendsProperty Instanz; Eine Klasse, die b2Body erweitert und eine neue Eigenschaft Point
  • hinzufügt
  • A Box2DCompositionProperty Instanz; Eine Klasse, die sowohl eine b2Body -Eigenschaft als auch eine Point -Eigenschaft
  • besitzt

Alle Tests wurden im Standalone-Player, FP v11.7.700.224, Windows 7, auf einem nicht sehr guten Laptop durchgeführt.

Test1: Größe

AS3 ist ein bisschen ärgerlich, wenn Sie getSize() aufrufen, wird Ihnen die Größe des Objekts selbst angezeigt, aber alle internen Eigenschaften, die auch Objects sind, ergeben nur einen 4 byte Zuwachs Zählen nur den Zeiger. Ich kann sehen, warum sie das machen, es macht es nur ein bisschen peinlich, die richtige Größe zu bekommen.

Also wandte ich mich an das flash.sampler -Paket. Wenn wir die Erstellung unserer Objekte testen und alle Größen in den NewObjectSample -Objekten addieren, erhalten wir die volle Größe unseres Objekts (HINWEIS: Wenn Sie sehen möchten, was erstellt wurde und die Größe, kommentieren Sie in log ruft die Testdatei auf).

  • Die Größe von Empty ist 56 // extends Object
  • b2Bodys Größe ist 568
  • Box2DExtends hat eine Größe von 568 // erweitert b2Body
  • Box2DExtendsOverrides Größe ist 568 // erweitert b2Body
  • Box2DCompositions Größe ist 588 // hat b2Body-Eigenschaft
  • Box2DExtendsProperty hat eine Größe von 604 // erweitert b2Body und fügt die Point-Eigenschaft
  • hinzu
  • Box2DCompositionProperty hat eine Größe von 624 // hat b2Body und Point-Eigenschaften

Diese Größen sind alle in Bytes. Einige bemerkenswerte Punkte:

  • Die Basis Object size ist 40 bytes, also nur die Klasse und nichts anderes ist 16 bytes.
  • Das Hinzufügen von Methoden erhöht nicht die Größe des Objekts (sie werden sowieso auf Klassenbasis implementiert), während Eigenschaften offensichtlich
  • tun
  • Die Erweiterung der Klasse hat nichts hinzugefügt
  • Die zusätzlichen 20 bytes für Box2DComposition stammen von 16 für die Klasse und 4 für den Zeiger auf die Eigenschaft b2Body
  • Für Box2DExtendsProperty etc haben Sie 16 für die Klasse Point selbst, 4 für den Zeiger auf die Eigenschaft Point und 8 für jede x und y Eigenschaft Numbers = 36 bytes Unterschied zwischen dieser und Box2DExtends

Der Unterschied in der Größe hängt also offensichtlich von den Eigenschaften ab, die Sie hinzufügen, aber alles in allem ziemlich vernachlässigbar.

Test 2: Erstellungsgeschwindigkeit

Dafür habe ich einfach getTimer() , mit einer Schleife von 10000 , selbst looped 10 (also 100k) verwendet, um den Durchschnitt zu erhalten. System.gc() wurde zwischen den einzelnen Gruppen aufgerufen, um den Zeitaufwand für die Garbage Collection zu minimieren.

  • Die Zeit für die Erstellung von Empty beträgt 3,9 ms (durchschnittlich)
  • b2Bodys Zeit für die Erstellung ist 65.5ms (av.)
  • Box2DExtends 'Zeit für die Erstellung beträgt 69,9 ms (durchschnittlich)
  • Box2DExtendsOverrides 'Zeit für die Erstellung beträgt 68,8 ms (durchschnittlich)
  • Box2DCompositions Zeit für die Erstellung ist 72.6ms (av.)
  • Box2DExtendsProperty's Zeit für die Erstellung ist 76.5ms (av.)
  • Box2DCompositionProperty hat eine Zeit von 77.2ms (av.)

Hier gibt es keinen ganzen Haufen. Die Erweiterungs- / Kompositionsklassen brauchen etwas länger, aber es ist wie 0.000007ms (dies ist die Erstellungszeit für 100.000 Objekte), also ist es nicht wirklich eine Überlegung wert.

Test 3: Anrufgeschwindigkeit

Dafür habe ich getTimer() erneut verwendet, mit einer Schleife von 1000000 , selbst gelooped 10 (also 10m) mal, um den Durchschnitt zu erhalten. System.gc() wurde zwischen den einzelnen Gruppen aufgerufen, um den Zeitaufwand für die Garbage Collection zu minimieren. Für alle Objekte wurden ihre getPosition()/GetPosition() -Funktionen aufgerufen, um den Unterschied zwischen Überschreiben und Umleiten zu sehen.

  • Die Zeit von empty für getPosition () ist 83.4ms (av.) // leer
  • Die Zeit von b2Body für GetPosition () beträgt 88.3ms (Av.) // normal
  • Die Zeit von Box2DExtends für getPosition () ist 158.7ms (av.) // getPosition () ruft GetPosition ()
  • auf
  • Box2DExtendsOverrides 'Zeit für GetPosition () ist 161ms (Av.) // Override-Aufrufe super.GetPosition ()
  • Box2DCompositions Zeit für getPosition () ist 160.3ms (av.) // ruft this.body.GetPosition ()
  • auf
  • Die Zeit von Box2DExtendsProperty für GetPosition () ist 89ms (av.) // implizit super (d. h. nicht überschrieben)
  • Box2DCompositionProperty's Zeit für getPosition () ist 155.2ms (av.) // ruft this.body.GetPosition ()
  • auf

Dieser hat mich etwas überrascht, mit dem Unterschied zwischen den Zeiten ~ 2x (obwohl das immer 0.000007ms pro Anruf ist). Die Verzögerung scheint vollständig auf die Klassenvererbung zurückzuführen zu sein - z. Box2DExtendsOverrides ruft einfach super.GetPosition() auf, ist aber doppelt so langsam wie Box2DExtendsProperty , das GetPosition() von seiner Basisklasse erbt.

Ich denke, es hat etwas mit dem Overhead von Funktions-Lookup und Calling zu tun, obwohl ich mir den generierten Bytecode mit swfdump im FlexSDK angeschaut habe, und sie sind identisch, also liegt es entweder an mir (oder auch nicht Wenn die Schritte gleich sein sollten, ist die Zeit zwischen ihnen wahrscheinlich nicht gleich (z. B. im Speicher, springt sie zu Ihrer Klassen-V-Tabelle und springt dann zur Basisklassen-V-Tabelle) usw.)

Der Bytecode für var v:b2Vec2 = b2Body.GetPosition() ist einfach:

%Vor%

während var v:b2Vec2 = Box2DExtends.getPosition() ( getPosition() gibt GetPosition() zurück) ist:

%Vor%

Im zweiten Beispiel wird der Aufruf von GetPosition() nicht angezeigt. Daher bin ich mir nicht sicher, wie sie das beheben. Die Testdatei steht zum Download bereit, wenn jemand bei der Erklärung einen Fehler machen möchte.

Einige Punkte, die Sie beachten sollten:

  • GetPosition() macht wirklich nichts; Es ist im Wesentlichen ein Getter, der als Funktion getarnt ist, was ein Grund dafür ist, dass die "Extra Class Step Penalty" so groß erscheint
  • Das war auf einer Schleife von 10m, die du in deinem Spiel wahrscheinlich nicht tust. Die pro-Anruf Strafe ist nicht wirklich die Mühe wert, sich über
  • Sorgen zu machen
  • Selbst wenn Sie sich Gedanken über die Strafe machen, denken Sie daran, dass dies die Schnittstelle zwischen Ihrem Code und Box2D ist; Die Box2D-Interna bleiben davon unberührt, nur die Aufrufe Ihrer Schnittstelle

Alles in allem würde ich die gleichen Ergebnisse erwarten, wenn ich einen meiner eigenen Kurse erweitern würde, also würde ich mir darüber keine Sorgen machen. Implementieren Sie die Architektur, die für Ihre Lösung am besten geeignet ist.

    
divillysausages 18.09.2013, 22:40
quelle
1

Ich weiß, dass diese Antwort nicht für das Kopfgeld geeignet ist, da ich viel zu faul bin, Benchmarks zu schreiben. Aber nachdem ich an der Flash-Codebasis gearbeitet habe, kann ich vielleicht ein paar Hinweise geben:

Das avm2 ist eine dynamische Sprache, daher wird der Compiler in diesem Fall nichts optimieren. Das Abwickeln eines Anrufs als Unterklassenanruf verursacht Kosten. Jedoch werden diese Kosten konstant zeit und klein sein.

Die Kosten der Objekterstellung werden höchstens von einer konstanten Menge an Zeit und Speicher beeinflusst. Auch die Zeit und der Betrag werden wahrscheinlich unbedeutend im Vergleich zu den Grundkosten sein.

Aber wie bei vielen Dingen steckt der Teufel im Detail. Ich habe box2d nie benutzt, aber wenn es irgendeine Art von Objekt-Pooling gibt, könnte es nicht mehr funktionieren. Im Allgemeinen sollten Spiele versuchen, ohne Objektzuweisungen zur Spielzeit zu laufen. Seien Sie also sehr vorsichtig, wenn Sie keine Funktionen hinzufügen, die Objekte zuweisen, um schöner zu sein.

%Vor%

Kann hässlich sein, ist aber viel schneller als

%Vor%

(Ich hoffe, ich habe meine AS3-Syntax richtig ...). Noch nützlicher und hässlicher könnte sein

%Vor%

Also meine Antwort ist, wenn Sie nur für Lesbarkeit wickeln, gehen Sie dafür. Es ist ein kleiner, aber konstanter Preis. Aber sei sehr, sehr vorsichtig, um zu ändern, wie Funktionen funktionieren.

    
starmole 15.09.2013 06:34
quelle
0

Ich weiß nicht, ob die Instanziierungszeit einen großen Einfluss hat, aber ich werde Ihre Frage anders beantworten: Was sind Ihre anderen Möglichkeiten? Scheinen sie, sie werden es besser machen?

Es gibt eine schöne Benchmark von Jackson Dunstan über die Funktionsleistung: Ссылка

Um es zusammenzufassen:

  • Schließungen sind teuer
  • static ist langsam: Ссылка
  • überschreiben, scheint das Aufrufen einer Funktion innerhalb einer Unterklasse keine großen Auswirkungen zu haben

Wenn Sie also keine Vererbung verwenden möchten, müssen Sie sie möglicherweise durch statische Aufrufe ersetzen, was die Leistung beeinträchtigt. Persönlich werde ich diese Klassen erweitern und eine eifrige Instanziierung aller Objekte hinzufügen, die ich zur Laufzeit benötige: Wenn es groß ist, mache einen schönen Ladebildschirm ...

Sehen Sie sich auch Post-Bytecode-Optimierungen an, z. B. apparat: Ссылка

    
zenbeni 15.09.2013 07:05
quelle
0

Ich glaube nicht, dass eine Verlängerung die Leistung stark beeinflussen wird. Ja, es gibt einige Kosten, aber es ist nicht so hoch, solange Sie keine Komposition verwenden. I.e. Anstatt Box2d-Klassen direkt zu erweitern, erstellen Sie eine Instanz dieser Klassen und arbeiten damit innerhalb Ihrer Klasse. Zum Beispiel das

%Vor%

statt dessen

%Vor%

Ich denke, Sie wissen, dass je weniger Objekte Sie erstellen, desto besser. Solange Sie die Anzahl der erstellten Instanzen beibehalten, haben Sie dieselbe Leistung.

Aus einem anderen Blickwinkel: a) das Hinzufügen einer weiteren Schicht von Abstraktionen kann die Box2d sehr verändern. Wenn Sie in einem Team arbeiten, kann das ein Problem sein, da die anderen Entwickler Ihre Benennung lernen sollten b) seien Sie vorsichtig mit Middle Man Code-Geruch. Wenn Sie mit dem Umschließen bereits vorhandener Funktionen beginnen, werden normalerweise Klassen zurückgegeben, die nur Delegatoren sind.

    
Krasimir 15.09.2013 07:25
quelle
0

Einige gute Antworten hier, aber ich werde meine zwei Cent hineinwerfen.

Es gibt zwei verschiedene Konzepte, die Sie erkennen müssen: Wenn Sie eine Klasse erweitern und implementieren eine Klasse.

Hier ist ein Beispiel für die Erweiterung von MovieClip

%Vor%

Hier ist ein Beispiel für die Implementierung von MovieClip

%Vor%

Ich denke, Sie möchten diese box2D-Klassen wahrscheinlich nicht erweitern, sondern implementieren. Hier ist ein grober Überblick:

%Vor%

Viel Glück.

    
Code Whisperer 19.09.2013 14:23
quelle