# Performanter lösbar?



## Azrahel (29. Nov 2006)

Hallo Brüder des Glaubens 

Ich hab vor ein paar Wochen mal angefangen ein kleines Spiel in Swing zu realisieren. läuft auch schon ganz gut finde ich. 

Um nun nach Performance-Löchern zu suchen hab ich hyades und das Java-Bordwerkzeug hrunXprof() (oder so ähnlich) benutzt. Dabei stellte sich heraus das die PaintComponent die zum Zeitpunkt des Speicherlesens durch beide Profiler meistbenutzte war. Wundert mich auf der einen Seite nicht, da sie direkt im Konstruktor mit nem Tread versehen wird der alle 10ms nen repaint() aufruft. Trotzdem wär ich echt happy wenn ihr auch mal draufgucken könnt.


```
public class Map extends JPanel { 
    ...
    
    /**
     * Konstruktor
     * 
     * wichtig: der Repaint-Thread wird sofort gestartet
     * @param lib
     */
    public ScrollingMap(Libration lib) { 
    	....
                if(t2!=null)t2.interrupt();
    	t2 = new Thread(rep);
    	t2.start();
    }


    /**
     * 
     * Zeichnen
     */
    public void paintComponent(Graphics g) { 
    	super.paintComponent(g); 
    	
        Graphics2D g2d = (Graphics2D)g; 
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        
        //Hintergrundverschiebung
        //-----------------------
        if(lib.getImc()!=null){
        	g2d.drawImage(lib.getImc().getImage("blue.jpg"), 
        				 ((lib.getPlayer().getXPos()%128)+128)*-1, 
        				 ((lib.getPlayer().getYPos()%128)+128)*-1, 
        				 null);}
        
    	//Sichtbare grenzen in der welt bestimmen
        //---------------------------------------
    	int xmin=lib.getPlayer().getXPos()-middlex;
    	int xmax=lib.getPlayer().getXPos()+middlex;
    	int ymin=lib.getPlayer().getYPos()-middley;
    	int ymax=lib.getPlayer().getYPos()+middley;
    	
    	//alle in world vorhandenen elemente auf sichtbarkeit prüfen
        //----------------------------------------------------------
    	for (int i=0;i<lib.getWorld().size();i++){
    		VisibleMapableObject vmo=(VisibleMapableObject)lib.getWorld().elementAt(i);
    		//auf alles ausser den Spieler reagieren
            //---------------------
    		if (vmo!=lib.getPlayer()) {
    			if(vmo.getXPos()>=xmin-middlex && 
        		   vmo.getXPos()<=xmax+middlex && 
        		   vmo.getYPos()>=ymin-middley && 
        		   vmo.getYPos()<=ymax+middley ){
        			g2d.drawImage(vmo.getImage(), 
        						  ((vmo.getXPos()-xmin)-(vmo.getImage().getWidth()/2)), 
        						  ((vmo.getYPos()-ymin)-(vmo.getImage().getHeight()/2)), 
        						  null);
        		}
			}    	
    	}

        //Zielflagge bei gesetztem kurs
        //-----------------------------
        if(lib.getPlayer().getXPos()!=lib.getPlayer().getKursx() || 
           lib.getPlayer().getYPos()!=lib.getPlayer().getKursy() ){
        	g2d.drawImage( lib.getImc().getImage("flags/flag.gif"), 
        				   lib.getPlayer().getKursx()-lib.getPlayer().getXPos() +middlex , 
        				   lib.getPlayer().getKursy()-lib.getPlayer().getYPos() +middley-lib.getImc().getImage("flags/flag.gif").getHeight()  , 
        				   null);
        	
        }
        //Spieler zeichen
        //Muss hier geschehen damit der Spieler 
        //NPC'S und andre PC'S überzeichnet
        //---------------
        g2d.drawImage(lib.getPlayer().getImage(), 
	    			  (middlex-(lib.getPlayer().getImage().getWidth()/2)), 
	    			  (middley-(lib.getPlayer().getImage().getHeight()/2)), 
	    			  null);
    } 
}
```


lib= BackBoneKlasse, ist überall in meinem Programm bekannt, damit ich manche Daten programmweit habe.
VisibleMapableObject = Jedes in meiner Karte sichtbare Object. kann xpos, ypos, Image (und deren Methoden).

Der im Konstruktor gestartete Thread ruft alle 10 ms repaint() auf, mein Bild wird also (wenn ich die Zeit für den Durchlauf der PaintComponent vernachlässige) 100mal/s neu aufgebaut.

nun bin ich mir aber nicht sicher ob ich die Auswertung in der PaintComponent machen soll oder besser auslagern.

Momentan frisst die Anwendung unter Last ca 40% CPU, in Ruhe ca 2%.   

Vielleicht sieht ja von euch jemand ob ich was verbessern kann. 

Viel Spass


----------



## Guest (30. Nov 2006)

Offscreen Image verwenden.


----------



## Azrahel (30. Nov 2006)

Anonymous hat gesagt.:
			
		

> Offscreen Image verwenden.



Ich nehm an ich du meinst ich soll mir ein neues ImageObject anlegen, das bemalen, und in der PaintComponent erst auf mein sichtbares Panel zeichnen nehm ich an. Die Idee hat ich auch schon, ich war mir nur nicht sicher ob das performancemäßig merklich was bringt. Aber ich werds mal ausprobieren, dann weiss ichs auch mit Sicherheit.

Danke schön für den Tip  :toll: 

Hat noch jemand Anregungen oder Kritik? (Ich hoff ja mal das Bleiglanz diesen Tread sieht, der iss immer so schön sarkastisch    )


----------



## Wildcard (30. Nov 2006)

Wenn, dann solltest du ein VolatileImage verwenden. Deutlich schneller als ein BufferedImage.
Aber ehrlich: Warum willst du optimieren wenn alles flüssig läuft?
40%/2% hört sich doch nicht schlecht an.


----------



## Azrahel (30. Nov 2006)

Hallo Wildcard 

Das mit dem VolatileImage klingt doch schon mal klasse, das test ich gleich mal aus  :lol: 

Ja, vorerst klingt das 40/2 recht gut, nur tut sich da noch nix drin ausser das der Spieler sich bewegen kann und seine Umgebung betrachten kann. Und die besteht im Moment nur aus Meer (Hintergrund) und Inseln dazu. Da sind dann noch keine Computergegner und andre Spieler dabei. Und ich hab halt Bammel was ist wenn sich dann mal 20-30 Objecte auf meinem Schirm tummeln. Die Hauptrechenarbeit würde dann zwar auf nem eigenen Server laufen, weil es soll ja Multiplayer werden, aber trotzdem. 

Bevor ich Mist programmiere, das toll finde weils ja rund läuft,  und mir das dann auch noch angewöhne frag ich lieber hier im Forum andre die meist mehr Ahnung haben als ich. Und wie man an deinem Tip und dem vom Gast sieht kann ich garantiert noch was dazu lernen.   Wirklich gut wird nämlich nur der der bereit ist Kritik zu bekommen und sein eigenes Werk, so stolz er darauf auch ist, mit dem Wissen anderer zu vergleichen. 

Ich probier das mit dem VolatileImage aus, und poste dann selbstverständlich das Ergebniss. Danke dir  :toll:


----------



## Azrahel (4. Dez 2006)

@Wildcard

Hab das WE über das ganze auf VolatileImage umgebaut, auch wenn mir dabei fast der Willi in die Wiese gefallen ist. Ergebniss: 36% / 2%   

Also fettesten dank (auch an alle andren)  :toll:


----------



## Wildcard (4. Dez 2006)

hmmm, berauschend ist das aber nicht. Schalte mal das double-buffering des JFrames aus, und sieh nach ob das noch was verbessert.


----------



## Azrahel (4. Dez 2006)

Uh, Mist jetzt haste mich erwischt. Ich wusst garnicht das man das auch gezielt ausschalten kann. Muss ich direkt mal googeln gehen. Ich hab die Software jetzt zuhause, probiere ich aber auch aus und poste dementsprechend wieder das Ergebniss 


Achso, Ähh die Werte beziehen sich auf meinen alten Acer, mit 800 MHZ und 500mb ram. Hier auf der Arbeit kann ich nicht testen, da hat mein Herr und Meister was dagegen.


----------



## EgonOlsen (4. Dez 2006)

Azrahel hat gesagt.:
			
		

> Der im Konstruktor gestartete Thread ruft alle 10 ms repaint() auf, mein Bild wird also (wenn ich die Zeit für den Durchlauf der PaintComponent vernachlässige) 100mal/s neu aufgebaut.


repaint() ist aber nur ein "Vorschlag", quasi eine Absichtserklärung deines Programms, dass der Eventthread beim nächsten Durchlauf ein Neuzeichnen veranlassen soll. Wenn du das alle 10ms (mal abgesehen davon, dass sein Thread.sleep(10); je nach System auch mal 15ms dauern kann) aufrufst, heißt das nicht, dass du auch 100 Bilder/sek bekommst. Muss jetzt nicht schlimm für dein Programm sein, ich wollte es nur mal gesagt haben... :wink:


----------



## Azrahel (5. Dez 2006)

@EgonOlsen

Du hast recht, der Code braucht ja auch zeit zum durchlaufen. Ich hab Ich hab mir aber echt noch nicht die Mühe gemacht mal nen Counter einzubauen der genau nachzählt. 

@Wildcard bin gestern nicht zum proggen gekommen zuhause, muss dich wegen dem Ergebniss leider auf morgen vertrösten.


----------



## Gast (5. Dez 2006)

Nein, es geht nicht um die Zeit zum Durchlaufen. Es geht darum, dass repaint() kein Neuzeichnen startet, sondern die Component nur auf die Liste der demnächst neu zu zeichnenden setzt.
Beispiel: Wenn vor einem Neuzeichnen einer Component durch den Eventthread 100mal repaint() aufgerufen wird, kommt dabei trotzdem nur ein tatsächlicher paint() heraus.


----------



## Azrahel (5. Dez 2006)

Hmm,  ???:L  da könnte (ich weiss es nicht genau) allerdings was wahres dran sein.

Kratzt aber eigentlich nur an meiner Aussage mit den 100 Repaints/sec, weil letzter Stand der Dinge war das es recht fluffig läuft. Auch wenn ich ein Performance-süchtiger bin   und man ja immer irgendwo noch ein bisschen rumtunen kann.


----------



## Wildcard (5. Dez 2006)

Nie die erste und wichtigste Regel der Performance-"Optimierung" vergessen:
1. *Don't*
 :wink:


----------



## WieselAc (5. Dez 2006)

Auch wenns wirklich nur ninimal ist und das vielleicht sogar vom Compiler gemacht wird, aber multiplizieren mit -1 ist theoretisch aufwendiger, als ein Vorzeichen Bit zu kippen. (zur Hintergrundverschiebung)


----------



## Guest (5. Dez 2006)

Azrahel hat gesagt.:
			
		

> Hmm,  ???:L  da könnte (ich weiss es nicht genau) allerdings was wahres dran sein.


Nicht nur könnte, es ist.  :wink: Das Zeichnen von Swing/AWT-Komponenten läuft immer (außer man übernimmt das komplett selber, was du aber nicht tust) im AWT-Eventthread. Das ist der, in dem auch Mouse- und KeyListener u.ä. benachrichtigt werden. Von daher KANN ein repaint(); gar nicht anders, als diesem Thread den Zeichenwunsch mitzuteilen und vor dem Neuzeichnen zurückzukehren. Denn zum einen läuft deine Applikation ja nicht im Eventthread sondern in ihrem eigenen und zum anderen mag der Eventthread gerade sonstwas machen, wenn der repaint(); kommt. Er kann also gar nicht sofort darauf reagieren, selbst wenn er wollte.


----------



## mamue (2. Jan 2007)

Das wird jetzt wahrscheinlich nicht der große Bringer sein, aber vielleicht ein kleines billiges Schnäppchen:
In den Zeilen 46/47 holst Du jeweils "lib.getWorld()". Kann man das vor die Schleife legen? In der for-Schleife holst Du für jeden Durchlauf die Größe (lib.getWorld().size()), kann man das vielleicht auch einmalig vor der Schleife berechnen?
Manchmal geht so etwas halt leider nicht, aber wenn, könnte es hier durchaus die eine oder andere Millisekunde sparen, nicht wahr?

HTH,
mamue


----------

