JavaScript-Code für statischen Arbeitsspeicher mit Objektpools

Colt McAnlis
Colt McAnlis

Einleitung

Sie erhalten also nach einer gewissen Zeit eine E-Mail, in der Sie darüber informiert werden, dass Ihr Webspiel bzw. Ihre Web-App eine schlechte Leistung erbringt, Sie durchsehen Ihren Code und sehen erst, wenn Sie die Tools zur Arbeitsspeicherleistung von Chrome öffnen, und Folgendes sehen:

Momentaufnahme der Arbeitsspeicherzeitachse

Ein Kollege kichert, weil er feststellt, dass Sie ein Leistungsproblem aufgrund des Arbeitsspeichers haben.

In der Speichergrafikansicht dieses Sägezahn-Musters weist dieses Sägezahn-Muster sehr auf ein potenziell kritisches Leistungsproblem hin. Wenn die Arbeitsspeichernutzung zunimmt, vergrößert sich auch der Diagrammbereich in der Zeitachse. Wenn das Diagramm plötzlich abfällt, ist dies eine Instanz, in der die automatische Speicherbereinigung ausgeführt wurde und die referenzierten Speicherobjekte bereinigt wurden.

Was die Sägezahn bedeuten

In einem solchen Diagramm sehen Sie, dass viele Ereignisse für die automatische Speicherbereinigung auftreten, was die Leistung Ihrer Webanwendungen beeinträchtigen kann. In diesem Artikel erfahren Sie, wie Sie die Speichernutzung kontrollieren und die Auswirkungen auf die Leistung reduzieren können.

Kosten für automatische Speicherbereinigung und Leistungsoptimierung

Das Speichermodell von JavaScript basiert auf einer sogenannten Garbage Collector. In vielen Sprachen ist der Programmierer direkt dafür verantwortlich, Arbeitsspeicher vom Arbeitsspeicher-Heap des Systems zuzuweisen und freizugeben. Ein System zur automatischen Speicherbereinigung verwaltet diese Aufgabe jedoch im Auftrag des Programmierers, d. h., Objekte werden nicht direkt vom Speicher befreit, wenn der Programmierer sie dereferenziert, sondern eher zu einem späteren Zeitpunkt, wenn die Heuristik der Speicherbereinigung dies als vorteilhaft beurteilt. Für diesen Entscheidungsprozess muss die automatische Speicherbereinigung einige statistische Analysen von aktiven und inaktiven Objekten durchführen, was einen Zeitblock in Anspruch nimmt.

Die automatische Speicherbereinigung wird oft als das Gegenteil der manuellen Speicherverwaltung dargestellt, bei der der Programmierer angeben muss, welche Objekte verworfen und an das Speichersystem zurückgegeben werden sollen.

Der Prozess, bei dem eine automatische Speicherbereinigung Arbeitsspeicher freigibt, ist nicht kostenlos. Sie reduziert die verfügbare Leistung in der Regel, indem sie einen Zeitblock für ihre Arbeit benötigt. Darüber hinaus entscheidet das System selbst, wann es ausgeführt werden soll. Sie haben keine Kontrolle über diese Aktion. Während der Codeausführung kann jederzeit ein GC-Puls auftreten, der die Codeausführung blockiert, bis sie abgeschlossen ist. Die Dauer dieses Taktes ist Ihnen im Allgemeinen nicht bekannt. Die Ausführung kann einige Zeit in Anspruch nehmen, je nachdem, wie Ihr Programm zu einem bestimmten Zeitpunkt den Arbeitsspeicher nutzt.

Anwendungen mit hoher Leistung sind auf konsistente Leistungsgrenzen angewiesen, um eine reibungslose Nutzung zu gewährleisten. Speicherbereinigungssysteme können dieses Ziel kurz schließen, da sie zu zufälligen Zeiten und für eine zufällige Dauer ausgeführt werden können, sodass die verfügbare Zeit genutzt wird, die die Anwendung benötigt, um ihre Leistungsziele zu erreichen.

Reduzieren Sie die Arbeitsspeicherabwanderung, senken Sie die Steuern für die automatische Speicherbereinigung

Wie bereits erwähnt, tritt ein GC-Puls auf, sobald eine Reihe von Heuristiken feststellt, dass genügend inaktive Objekte vorhanden sind, die ein Impuls vorteilhaft wäre. Der Schlüssel zum Reduzieren der Zeit, die die Speicherbereinigung für Ihre Anwendung benötigt, besteht also darin, so viele Fälle der übermäßigen Erstellung und Freigabe von Objekten wie möglich zu eliminieren. Dieses häufige Erstellen/Freigeben von Objekten wird als „Arbeitsspeicherabwanderung“ bezeichnet. Wenn Sie die Speicherabwanderung während der Lebensdauer Ihrer Anwendung reduzieren können, reduzieren Sie auch die Zeit für die Ausführung von GC. Das bedeutet, dass Sie die Anzahl der erstellten und zerstörten Objekte entfernen / verringern und die Zuweisung von Arbeitsspeicher beenden müssen.

Durch diesen Vorgang wird Ihr Speicherdiagramm von diesem verschoben :

Momentaufnahme der Arbeitsspeicherzeitachse

in:

JavaScript für statischen Arbeitsspeicher

In diesem Modell können Sie sehen, dass der Graph nicht mehr ein Sägezahnmuster mehr aufweist, sondern vielmehr zu Beginn stark wächst und dann im Laufe der Zeit langsam ansteigt. Wenn Sie aufgrund von Speicherabwanderung auf Leistungsprobleme stoßen, ist dies die Art von Grafik, die Sie erstellen sollten.

Auf JavaScript mit statischem Arbeitsspeicher übergehen

JavaScript für statischen Arbeitsspeicher ist eine Technik, bei der zu Beginn Ihrer App der gesamte Arbeitsspeicher, der für die Lebensdauer benötigt wird, vorab zugewiesen wird. Außerdem wird dieser Arbeitsspeicher während der Ausführung verwaltet, da keine Objekte mehr benötigt werden. Dazu sind ein paar einfache Schritte erforderlich:

  1. Instrumentieren Sie Ihre Anwendung, um zu ermitteln, wie viele Live-Speicherobjekte (pro Typ) für eine Reihe von Nutzungsszenarien maximal benötigt werden.
  2. Implementieren Sie Ihren Code neu, um diese maximale Menge vorab zuzuordnen. Rufen Sie die Codes dann manuell ab oder geben Sie sie frei, statt in den Hauptspeicher zu wechseln.

Um Punkt 1 zu erreichen, müssen wir jedoch auch Teil 2 machen. Fangen wir also mit diesen an.

Objektpool

Einfach ausgedrückt ist Objekt-Pooling der Prozess, bei dem eine Reihe nicht verwendeter Objekte beibehalten wird, die denselben Typ haben. Wenn Sie ein neues Objekt für Ihren Code benötigen, können Sie eines der nicht verwendeten Objekte aus dem Pool wiederverwenden, anstatt ein neues aus dem Arbeitsspeicher-Heap des Systems zuzuweisen. Wenn der externe Code mit dem Objekt fertig ist, wird er an den Pool zurückgegeben, anstatt ihn an den Hauptarbeitsspeicher freizugeben. Da das Objekt nie aus Code dereferenziert (d. h. gelöscht) wird, wird es nicht automatisch bereinigt. Die Verwendung von Objektpools gibt dem Programmierer wieder die Kontrolle über den Arbeitsspeicher und verringert den Einfluss der Speicherbereinigung auf die Leistung.

Da es eine heterogene Gruppe von Objekttypen gibt, die eine Anwendung verwaltet, erfordert die ordnungsgemäße Nutzung von Objektpools, dass Sie einen Pool pro Typ haben, bei dem es während der Laufzeit Ihrer Anwendung zu einer hohen Abwanderung kommt.

var newEntity = gEntityObjectPool.allocate();
newEntity.pos = {x: 215, y: 88};

//..... do some stuff with the object that we need to do

gEntityObjectPool.free(newEntity); //free the object when we're done
newEntity = null; //free this object reference

Bei den meisten Anwendungen werden Sie irgendwann ein gewisses Level erreichen und müssen dann neue Objekte zuweisen. Bei mehreren Ausführungen Ihrer Anwendung sollten Sie einen Eindruck von der Obergrenze bekommen und diese Anzahl von Objekten zu Beginn der Anwendung vorab zuweisen können.

Vorabzuweisung von Objekten

Durch die Implementierung von Objekt-Pooling in Ihrem Projekt erhalten Sie ein theoretisches Maximum für die Anzahl der Objekte, die während der Laufzeit Ihrer Anwendung benötigt werden. Sobald Ihre Website verschiedene Testszenarien durchläuft, können Sie sich ein gutes Bild von den erforderlichen Speicheranforderungen machen, diese Daten katalogisieren und analysieren, um die Obergrenzen des Arbeitsspeicheranforderungen für Ihre Anwendung zu verstehen.

Dann können Sie in der Versandversion Ihrer App die Initialisierungsphase so einstellen, dass alle Objektpools mit einem bestimmten Betrag vorausgefüllt werden. Durch diesen Vorgang wird die gesamte Objektinitialisierung in den Vordergrund Ihrer App verschoben und die Anzahl der Zuweisungen, die während der Ausführung dynamisch erfolgen, reduziert.

function init() {
  //preallocate all our pools. 
  //Note that we keep each pool homogeneous wrt object types
  gEntityObjectPool.preAllocate(256);
  gDomObjectPool.preAllocate(888);
}

Der Betrag, den Sie wählen, hat großen Einfluss auf das Verhalten Ihrer Anwendung. Manchmal ist das theoretische Maximum nicht die beste Option. Wenn Sie beispielsweise den durchschnittlichen Maximalwert auswählen, sinkt der Arbeitsspeicherbedarf für Nicht-Poweruser möglicherweise.

Weit von einer Spitzenklasse entfernt

Es gibt eine ganzheitliche Klassifizierung von Apps, bei denen das Wachstumsmuster des statischen Arbeitsspeichers von Vorteil sein können. Wie auch von Renato Mangini aus dem Chrome-Entwicklerteam erwähnt, gibt es jedoch auch ein paar Nachteile.

Fazit

Ein Grund dafür, warum JavaScript ideal für das Web ist, liegt in der Tatsache, dass es eine schnelle, unterhaltsame und einfache Sprache für den Einstieg ist. Dies liegt vor allem an der geringen Hürde gegenüber Syntaxbeschränkungen und der Handhabung von Speicherproblemen für Sie. Sie können einfach programmieren und ihn sich die schmutzige Arbeit überlassen. Bei leistungsstarken Webanwendungen wie HTML5-Spielen kann die automatische Speicherbereinigung jedoch häufig die dringend benötigte Framerate zunichte machen, was die Nutzerfreundlichkeit verringert. Mit einer sorgfältigen Instrumentierung und Einführung von Objektpools kannst du diese Belastung deiner Framerate reduzieren und diese Zeit für noch bessere Funktionen nutzen.

Quellcode

Es gibt viele Implementierungen von Objektpools, die im Web schwimmen, also erzähle ich Ihnen nicht noch eine weitere. Stattdessen werde ich Sie auf diese Punkte verweisen, für die es jeweils spezifische Nuancen bei der Implementierung gibt. Das ist wichtig, da jede App-Nutzung spezifische Implementierungsanforderungen hat.

Verweise