Szdnez01: Extremer Speicherhunger beim Zeichnen auf einem Canvas

Hallo Zusammen,

ich habe eine Anwendung geschrieben, die ein stark erweiteres Gantt Diagramm mit diversen Interaktionsmöglichkeiten darstellt. Realisiert habe ich das ganze mittels GWT, wobei das Diagramm selbst auf einem großen Canvas dargestellt wird. Soweit funktioniert das ganze auch gut. Allerdings habe ich manchmal das Problem, dass die Anwendung (bzw. Chrome, in dem die Anwendugn läuft) in sehr kurzer Zeit extrem viel Hauptspeicher benötigt, teilweise mehrere 100 MB, wobei der JavaScript Speicher nach dem Chrome Taskmanager nahezu konstant bleibt. Bspw. kann man in dem Diagramm einzelnen Rechtecke über einen Mausklick selektieren. Da die Rechtecke auch übereinander liegen können ermittle ich für diesen Fall zunächst alle Rechtecke etc. auf dem Canvas, die mit dem zu selektierendem Rechteck in Berührung sind (was bspw. eine gesamt neu zu zeichnende Fläche von 60 x 20 px ergibt). Diese Objekte werden dann alle neu gemalt, wobei zuvor noch der Inhalt des zu ändernden Bereichs gelöscht wird und das selektierte Element bekommt eine andere Farbe. Führe ich diese Operation auf einem Canvas aus, dass meinetwegen 2000 x 1000 px groß ist, so steigt der Gesamtspeicherverbrauch um 50 MB an (JavaScript Speicher ist nahezu fix). Hat das Canvas aber eine Größe von 10000 x 3000 px, so steigt der Speicherbrauch um mehrere 100 MB. Was passiert da? Und noch viel wichtiger, was kann ich dagegen tun? Interessanterweise steigt der Verbrauch bei einer Wiederholung der Aktion nicht weiter an bzw. manchmal entstehen auch einfach nur Speicherspitzen für kurze Zeit um mehrere 100 MB. Ich vermute, dass es etwas mit der Canvasgröße und den Zeichenoperationen auf dem Canvas zu tun hat, da der Speicherverbrauch augenscheinlich mit der Canvasgröße zusammenhängt und der JavaScript Speicher nahezu unverändert bleibt. Oder gibt es noch andere Möglichkeiten? Kann man irgendwie herausfinden, wo der Speicher bleibt? Mit den Chrome Entwicklertools hat man ja nur Einblick auf den JavaScript Teil. Und das Chrome Trace Tool kann das vermutlich, aber ohne nichts über Chrome Interna zu wissen scheint mir das Tool kaum benutzbar sein. Wenn es an den Zeichenoperationen auf dem Canvas liegt, was kann ich ändern, was sollte ich anders machen? Ich habe auch schon ein wenig quer gelesen bzgl. Canvasoptimierungen (wobei diese meist in Hinblick auf Spiele konzipiert waren, aber hier sicherlich auch zur Anwendung kommen können und eigentlich immer nur das Laufzeitverhalten betrachtet wurde), wobei mir drei Ansätze ins Auge gefallen sind:

  1. Hintergrund Rendering: Ich erzeuge ein zweites Canvas, welches nur die Größe der neu zu zeichnenden Objekte hat und male diese auf das neue Canvas, anschließend wird das neue Canvas an die richtige Position im richtigen/eigentlichen Canvas gemalt

  2. Multi Canas: Wieder ein zweites Canvas wie unter 1. erstellen, dieses aber auch direkt in die HTML Seite an der richtigen Position einfügen, so dass am Ende zwei Canvas existieren

  3. Das Canvas hat eine feste Größe, bspw. Bildschirm, und es wird immer nur ein Ausschnitt des kompletten Diagramms gezeichnet. Wenn man dann scrollt, so muss man die noch fehlenden Teile ergänzen (so in der Art arbeitet vermutlich auch das Trace Tool, oder?). Alle Änderungen erfolgen dann entweder direkt auf diesem Canvas oder auch wieder wie 1. bzw. 2.

  4. wäre noch halbwegs einfach umzusetzen in meiner Anwendung, 2. schon ein weniger komplizierter und 3. sehr aufwendig. Falls es zu Drittens kommt, gibt es da irgendwelche Unterstützung durch das Framework seitens GWT oder HTML/Canvas? Wie würde man das mit den Scrollern machen, da dann mein Canvas ja immer die gleiche Größe hätte? Wie macht es das Trace Tool?

Ist eine der drei Strategien zielführend? Gibt es noch weitere Möglichkeiten? Denn ich muss den Speicherverbrauch irgendwie reduzuieren, da es auch häufig vorkommt, dass der Browser komplett aussteigt (entweder es kommt zu einer allgemeinen Fehlermeldung, oder es kommt ein Fehler, das der Arbeitsspeicher überschritten ist oder die komplette HTML-Darstellung wird schwarz (also wirklich der komplette Bereich im Browser, in dem die Internetseiten dargestellt werden) und dies vermutlich an dem Speicherverhalten liegt.

Vielen Dank für jedwegige Hilfe, Szdnez

  1. Hi,

    viel kann ich hier nicht beisteuern, nur das: Der CanvasRenderingContext2D bietet eine recht simple Programmier-Schnittstelle, dafür opfert man Leistungsfähigkeit. Für performance-kritische Anwendungen ist der WebGLRenderingContext gedacht. Eine weitere Alternative ist SVG, das skaliert nicht mit der Anzahl der Pixel, sondern mit der Anzahl der Objekte in der Szene. Für interaktive Diagramme ist SVG prädestiniert.

    1. Hallo,

      danke für die Hinweise, über den WebGLRenderContext bin ich bisher noch nicht gestolpert. Das erste Framework, dass wir uns damals für diesen Anwendungsfall geschrieben hatten, ca. 2-3 Jahre her, basierte auf SVG. Allerdings hatten wir bei komplexen Grafiken massive Performanceprobleme hinsichtlich dem Laufzeitverhalten, da in der Grafik auch gerne mal 20.000 Objekte darzustellen sind. Deswegen haben wir mit dem Canvas angefangen, was anfangs auch gut aussah, bis die Speicherprobleme aufgetreten sind. Aber vielleicht ist SVG ja mittlerweile besser geworden… Auf jeden Fall werde ich mir bei Gelegenheit mal den WebGLRenderContext genauer ansehen, vielleicht ist er ja für unsere Zwecke besser geeignet und lässt sich halbwegs schmerzfrei einbinden. Ansonsten bin ich durch weitere Experimente zu der Überzeugung gelangt, dass vermutlich nur Variante 3 eine zufriedenstellende Abhilfe bringen wird. Bei Bedarf kann ich auch noch genauer ausführen, warum, wieso, weshalb…

      Gruß, Szdnez

  2. Hallo Szdnez01,

    Führe ich diese Operation auf einem Canvas aus, dass meinetwegen 2000 x 1000 px groß ist, so steigt der Gesamtspeicherverbrauch um 50 MB an (JavaScript Speicher ist nahezu fix). Hat das Canvas aber eine Größe von 10000 x 3000 px, so steigt der Speicherbrauch um mehrere 100 MB. Was passiert da?

    Es skaliert. 2 Millionen Pixel zu 30 Millionen Pixel verhält sich wie 50 MB zu „mehrere[n] 100 MB“. Insofern verstehe ich das „aber“ nicht.

    MfG, at