# Effizient zeichnen



## tuxedo (20. Sep 2006)

Hallo,
ich habe folgende Problem:

Ich bastle an einem Programm das mit GPS Wegpunkte aufzeichnet, in eine DB speichert und auch wieder im Programm anzeigt. Anfangs ging das noch ziemlich gut und auch schnell. Doch mittlerweile wachsen die Daten und ich stoße an die Grenzen meines Wissens.

In einem JPanel hab ich die paintMethode überschrieben und zeichne mit g.drawLine() meine aufgezeichneten Wege. 

Das sieht z.B. so aus (hab mal nur die Karte aus dem Programm ausgeschnitten):







Aktuell sind auf dem Bild über 3000 Wegpunkte zu sehen die mit einer Linie verbunden sind. Insgesamt zeigt das Bild eine ungefähre entfernung von 20km von unten links nach oben rechts an. D.h. wenn ich in der Ortschaft unten links oder oben rechts noch mehr Daten gesammelt hätte würde er noch mehr zeichnen, auch wenn man die einzelnen Straßen wohlmöglich nur schwer erkennen könnte. Eigtl ist das sehr ineffizient. Aber siehe weiter unten bei meiner abschließenden Frage....


Meine paintMethode mach im Prinzip nur das hier:

+ Nimm den Vector in dem alle miteinander assoziierten Pixel sind und arbeite diesen Element für Element ab:
+ Wenn das mit g.drawLine zu zeichnenden Pixel-Paar im Ausschnitt der Karte liegt, zeichne dies.

Ich hab hier bewusst mal den Quellcode weggelassen da ich hier alles aufs allernötigste reduziert hab um möglichst jedes instanziieren einer Hilfsvariable zu vermeiden, um Zeit zu sparen. Sieht also mehr oder weniger ganz wild aus. 

Der Vector enthät nix weiter wie eigene Pixel-Objekte in denen steht welche 2 Pixel miteinander verbunden werden müssen.

Der Vector wird in einer Endlosschleife immer wieder mit der DB angeglichen und enthält mehr Daten als aktuell gezeichnet werden müssen. Das dient dazu nicht bei jedem Zeichenvorgang eine Anfrage an die DB stellen zu müssen. 

Die endlosschleife schiebt bei jedem durchlauf den Vector mit den Daten an das JPanel das die karte darstellt. Nach dem weitergeben der Daten erfolgt ein "globaler" repaint(). Erst dachte ich dass das erneute zeichnen des ganzen programmfensters so viel leistung frisst und hab mal testweise mit meine Karte neu zeichnen lassen (mapPanel.repaint()), aber das brachte keinen sichbaren Erfolg.

Dann habe ich Stück für Stück Programmteile abgeschaltet um zu sehen an welchem Teil die meisten Ressourcen drauf gehen. Und tatsächlich: Es liegt an der paintMethode die einfach viel zu viel zeichnen muss. 

Ich muss dazu sagen: Die Endlosschleife hat am Ende ein sleep(1) um die CPU nicht vollends zu vereinnahmen. 
Getriggert wird die Endlosschleife allerdings durch den simulierten Empfang der GPS Daten. D.h. im Moment kommen alle 10ms neue GPS-Daten rein so dass, grob gesagt, alle 10ms ein repaint() erfolgen muss um die Karte auf dem laufenden zu halten. 

Für den realfall der Anwendung mag das noch schnell sein. Aber im realfal hat man auch schnell mehr zu zeichnen weil einfach mehr Daten vorhanden sind. 

Ok, kommen wir zur eigentlichen Frage:

Bietet Java oder irgendeine 2D API etwas schnelleres als g.drawLine()? 

Oder gibt es einen Algorithmus der mir da weiterhelfen könnte (siehe Problem weiter oben mit dem zu viel zeichnen das man eh nicht erkennen kann)?

gruß
Alex

P.S. Sorry wenn ich das falsche Sub-Board erwischt hab. Alle anderen schienen mir nicht zu passen.


----------



## AlArenal (20. Sep 2006)

Endlosschleifen sind schonmal pisse. Gezeichnet werden sollte nur dann, wenn gezeichnet werden muss, d.h. wenn sich entweder an den Daten etwas ändert, oder der Controller den View entsprechend beauftragt, weil wer scrollt, zoomt, etc. 
Daher grundsätzlich: MVC ansehen, verinnerlichen, einbauen!

Was Java2D angeht, bin ich zwar keine Guru, was die interne Implementierung in der VM angeht, würde aber darauf wetten dass es füür derart komplexe Strukturen performanter ist einen GeneralPath zu benutzen, anstatt tausende Linien einzeln zu zeichnen.


----------



## tuxedo (20. Sep 2006)

Servus,
danke für die schnelle Antwort. Aber ich könnt mir selbst in den Hintern beissen....

Statt Einfach nur "Graphics"  zu verwenden hab ich eben mal kurz gegoogelt und das hier eingebaut:


```
Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
```

Und statt mit "g" hab ich dann mit "g2" gezeichnet. 
Und siehe da: Das Ding rennt wie sau. 
"Wie sau" bedeutet:
Zwar noch immer eine sehr hohe CPU-Last, aber die Anzeige ruckelt nichtmehr, egal ob ich jetzt 1000 oder 5000 Wegpunkte zeichne. Und die GUI reagiert viel schneller auf Benutzereingaben wie verändern des Zooms...

Was die Endlosschleife angeht:
Das hast du wohl nicht ganz verstanden oder ich habs schlecht erklärt (wohl eher letzteres, schreibe ja nicht zum ersten mal Programme):

Ich schreibs mal im Pseudo-Code nieder:


```
while(true){

if (buffer!=empty) {
getDataFromBuffer();
putDataIntoDB();
showOldAndNewDataOnMap();
}
sleep(1);
}
```

Also nur wenn in meinem Empfangspuffer wirklich Daten da sind, werden diese auch abgeholt, verarbeitet und gezeichnet.
Das Prinzip des MVC ist mit durchaus bekannt. Jedoch ist das standard-MVC-Vrfahren wie man es in vielen Büchern nachlesen und in einigen Vorlesungen hört nicht immer anwendbar (nicht immer heisst nicht "nie"). 

Was GeneralPath betrifft: 
Hör ich jetzt zum ersten mal. Hab gleich google angeworfen und die ersten Brocken gelesen. Hört sich interessant an. Mal sehen wie ich das einbauen kann. 
Danke hierfür. Man lernt eben nie aus.

gruß
Alex


----------



## AlArenal (20. Sep 2006)

Und wenn du das Antialiasing ausschaltest, gehts noch schneller 
Gern genommener Kniff ist z.B. während aufwändiger Opertationen, z.B. wenn man scrollt, live zoomt oder animiert, das Antialiasing auszuschalten (weil es heftig Performance kostet) und dann am Ende wieder zu aktivieren, wenn nur noch der 'Endzustand' gezeichnet wird (letzter Frame der Animation, kein weiterer Zoom, Endpunkt des Scrollings erreicht, ...).

Nochmal zu deiner Schleife:
Wäre es nicht sinniger, wenn der Buffer per Event meldet, wenn er neue Daten hat? Wenn ich das sleep sehe, wird mir beim Gedanken daran den AWT Event Dispatcher Thread schlafen zu legen irgendwie unwohl..


----------



## tuxedo (20. Sep 2006)

Du wirst mir das jetzt vielleicht nicht glauben, aber wenn ich

```
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
```
 mache und somit AA generell abschalte ruckelts wieder wie vorher....
Vielleicht mach ich da ja was falsch?

Das mit dem Buffer und dem Event:
Der Buffer dient mir dazu den Empfangsthread von der GUI abzukoppeln. Ich hätte ja ach gleich Empfang mit der GUI direkt verbinden können. Aber wenn das eine mal hängt beeinträchtigt es das andere. Dank dem Buffer hab ich hier Spielraum. Hängt die GUI mal kann ich noch weiter Empfangen.

Mit dem sleep(1) lege ich ja auch nicht die GUI schlafen. Ich habe Logik, Empfangstel und GUI je in eine Klasse gesteckt und über eine Starter-Klasse diese  3 Instanziiert und miteinander verbunden, sprich die nötigen Referenzen untereinander gesetzt. Die GUI instanziiert sich noch weitere Klassen, so z.B. den Tacho oder auch die Karte...
Wenn ich jetzt im Logikteil ein sleep(1) mache lege ich doch nicht die GUI schlafen? Oder bin ich da jetzt falsch?


----------



## AlArenal (20. Sep 2006)

Wenn du nicht explizit mit Threads arbeitest läuft bei dir alles im AWT Event Dispatcher Thread (meist kurz EDT) und der ist fürs Zeichnen und die Event-Verarbeitung zuständig. Legst du den Thread schlafen, oder machst du sonst etwas, was unbestimmt viel Zeit benötigt (I/O jeder Art, wie DB-Abfragen, Datei I/O, Netzwerkkram, ..) werden in der Zeit keine Events verarbeiet und es wird auch nicht neu gezeichnet.
Derart murksig entwickelte Software bringt viele immer dazu zu sagen Java/Swing sei langsam, ganz einfach weil der User dann diverse Verzögerungen bemerkt.


----------



## tuxedo (20. Sep 2006)

hi,
okay, wieder was gelernt. D.h. wenn ich sleep behalten will muss meine Logikklasse auch als Thread laufen. Dann kann ich explizit diesen Thread schlafen legen (this.sleep(1)), richtig?

Muss ich dazu meine GUI-Klasse (extends JFrame) auch nochmal explizit als Thread laufen lassen oder läuf die swing-technisch schon ausreichend genug als Thread?

Wie war das jetzt mit dem AA? Hast du ne Ahnung warum das bei mir abgeschaltet langsamer läuft als angeschaltet?


----------



## AlArenal (20. Sep 2006)

alex0801 hat gesagt.:
			
		

> hi,
> okay, wieder was gelernt. D.h. wenn ich sleep behalten will muss meine Logikklasse auch als Thread laufen. Dann kann ich explizit diesen Thread schlafen legen (this.sleep(1)), richtig?



Yo. Wobei mir der Sinn des sleep noch abgeht.



> Muss ich dazu meine GUI-Klasse (extends JFrame) auch nochmal explizit als Thread laufen lassen oder läuf die swing-technisch schon ausreichend genug als Thread?



Ich hole mal was aus:
Standardmäßig hat eine Swing-Anwendung zwei Threads, den Main Application Thread, der die #main ausführt und den EDT, der Event-Handling, Zeichnen und Layout übernimmt. Alles was mit dem GUI zu tun hat, sollte auch im EDT erledigt werden, d.h man sollte auf die Idee kommen den Text eines Labels in einem anderen Thread zu setzen. In dem Zusammenhang empfehle ich immer sich folgendes auch ruhig mehrmals in Ruhe anzuschauen:
http://www.javalobby.org/eps/galbraith-swing-2/



> Wie war das jetzt mit dem AA? Hast du ne Ahnung warum das bei mir abgeschaltet langsamer läuft als angeschaltet?



Nope. Klingt für mich nicht logisch. Kann höchstens sein, dass das eine bei dir hardware-beschleuningt ist und das andere nicht, nur macht es sorum m.E. keinen Sinn *achselzuck*


----------



## tuxedo (20. Sep 2006)

Der Link ist klasse. Ich werds mir reinziehen.

So far: thanks a lot  ...

- Alex

[UPDATE]
Nochmals vielen Dank für den Link. Hab die erste Präsentation auch angesehen. Der Typ hat mich begeistert. Is schon krass was man mit nur wenigen Zeilen zusätzlichen Code bewirken kann. Ist die Präsentation schon irgendwo hier im Forum bei den FAQs oder so verlinkt? Denke speziell Teil2 sollte sich jeder reinziehen der gute GUIs basteln will.


----------



## tuxedo (23. Sep 2006)

Okay, hab weitere Tests gemacht, meine Logik von der GUI mittels eigenem Thread abgekoppelt etc...

Ein problem hab ich aber noch:
Da alles zeichnen im EDT passiert und ich ne Menge zu zeichnen hab legt das zeitweise die restliche GUI lahm...

Gibts ne Möglichkeit die GUI von meiner Karten-Komponente die so viel zu tun hat zu entkoppeln?!
Sprich: Kann ich das zeichnen dieser einen Komponente in nem anderen Thread als dem EDT laufen lassen?

Oder hab ich keine andere Wahl als meine Zeichenroutine so schnell zu machen dass der EDT keine Performanceprobleme damit bekommt?

- Alex


----------



## tuxedo (23. Sep 2006)

Hätte doch früher schon mich mit GenerelPath befassen sollen. Hab vor dem zeichnen meinen Vector in Generalpath gesteckt und nur noch ein 


```
g2.draw(map);
```

gemacht. Und siehe da: Das geht sau schnell. Und wenn ich AA jetzt ausschalte gehts, wie du anfangs schon sagtest, nochmal schneller. 

Jetzt kann ich auch die ganze Karte mit aktuell rund 5000 Wegpunkten im 5ms Takt neu zeichnen ohne dass beim rein oder rauszoomen eine Verzögerung der GUI erkennbar ist. Okay, die CPU-Last ist immernoch bei 70% (3,1Ghz Sempron), aber im echten Leben liefert der GPS Empfänger die Signale auch nur rund alle 277ms. 
Vielen Dank nochmals für die Tipps,

gruß
Alex


----------



## transsib (6. Jan 2007)

Hallo Alex!

Verrätst Du mir, welches GPS Du verwendest? Weiter oben schreibst Du, dass es mit bis zu 100 Hz (alle 10 ms) sendet. Da kannte ich bislang nur die VBox, die das so schnell schafft!?

Weshalb zeichnest Du die Karte alle 5 ms neu? Das menschliche Auge kann doch ohnehin maximal 25 Bilder in der Sekunde, also alle 40 ms ein Bild, wahrnehmen. Selbst Bewegungen mit 10 Bildern pro Sekunde werden noch als einigermaßen "weich" wahrgenommen".

Neugierige Grüße,

Oliver


----------



## tuxedo (8. Mrz 2007)

Da man ja schlecht beim Programmieren mit dem GPS-Empfänger durch die gegend rennen kann haben wir nen GPS-Satz-Simulator gebastelt. Das ist auch ganz praktisch um aufgezeichnete Fahrten nochmal abzuspielen. Und da kommen die Sätze so schnell weswegen auch so oft hintereinander gezeichnet wird. 

Aber ich tippe mal darauf dass du meinen letzten Post nicht aufmerksam gelesen hast. Da gings nur drum DASS es jetzt möglich ist so schnell zu zeichnen.
Und da steht auch das der echte Empfänger nur alle ~277ms ein GPRMC-Satz liefert, und nicht alle 10ms


----------

