Swing setLocation Aufruf in JApplets 20 mal langsamer als in JFrames

javimka

Top Contributor
Mir ist etwas seltsames aufgefallen, für das ich keine Erklärung finde. Ich habe eine Klasse TimerPanel, die von JPanel erbt. Darin fliegen 1000 ganz billige Bälle umher, die von JComponent erben. Die neue Position dieser Bälle, wird mit setLocation(x,y) gesetzt. Mit System.nanoTime() werden die Zeiten, die die setLocation Aufrufe benötigen gemessen und anschliessend angezeigt.

Wenn ich das Panel nun in ein JFrame lege, dauern die Aufrufe von setLocation bei mir 3 ms. Wenn ich das gleiche Panel in ein JApplet lege, dauert es 60 ms, also 20 Mal mehr. Wieso?

Folgendes Programm kann als Applikation mit einem JFrame oder als JApplet gestartet werden. In beiden Fällen, wird eine Instanz von TimerPanel erstellt und in den jeweiligen Container gelegt.
Java:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.JApplet;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class TimerApplet extends JApplet {

	// setup frame
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				JFrame frame = new JFrame("Performance Test Frame");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				frame.add(new TimerPanel());
				frame.pack();
				frame.setLocationRelativeTo(null);
				frame.setVisible(true);
			}
		});
	}

	// setup applet
	public void init() {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				getContentPane().add(new TimerPanel());
				setSize(getPreferredSize());
			}
		});
	}
	
	/**
	 * Panel für die Bälle, das auch die Zeit misst
	 */
	private static class TimerPanel extends JPanel {

		private static final int MAX_SPEED = 250; // pixel per second
		private static final int INIT_BALLS = 1000;
		private static final int INIT_DELAY = 20; // in ms

		// step time
		private long locdt; // meassured in ns
		
		private int n;
		private ArrayList<Ball> balls;
		
		public TimerPanel() {
			// setup panel
			setLayout(null);
			setBackground(Color.WHITE);
			setSize(600,400);
			setPreferredSize(new Dimension(600,400));
			
			// init balls
			balls = new ArrayList<Ball>();
			addRandomBalls(INIT_BALLS);
			
			// init timer
			new Timer(INIT_DELAY,new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					step(INIT_DELAY);
					repaint();
				}
			}).start();
		}
		
		public void paint(Graphics g) {
			super.paint(g);
			g.setColor(Color.RED);
			g.drawString("Bälle: "+n,0,12);
			g.drawString("Bälle: "+n,1,12);
			g.drawString("setLocation: "+locdt/1000000+" ms",0,24);
			g.drawString("setLocation: "+locdt/1000000+" ms",1,24);
		}

		/**
		 * Bewege alle Bälle so weit, wie sie in der Zeit dt kommen. Summiere
		 * dabei die benötigte Zeit für die setLocation Aufrufe auf
		 * @param dt - wie viele Millisekunden sich die Bälle bewegt haben
		 */
		private void step(long dt) {
			locdt = 0; // reset time
			for (Ball ball:balls) {

				locdt += ball.step(dt); // sum time for setLocation calls
				
				// collision with border handling
				if (ball.vx<0 && ball.getX()<0) ball.vx = -ball.vx;
				if (ball.vy<0 && ball.getY()<0) ball.vy = -ball.vy;
				if (ball.vx>0 && ball.getX()+ball.size>=getWidth()) ball.vx = -ball.vx;
				if (ball.vy>0 && ball.getY()+ball.size>=getHeight()) ball.vy = -ball.vy;
			}
		}
		
		/**
		 * Füge m Bälle hinzu
		 */
		public void addRandomBalls(int m) {
			Random rand = new Random();
			for (int i=0;i<m;i++) {
				Ball ball = new Ball(rand.nextInt(600),
						rand.nextInt(400),
						rand.nextInt(MAX_SPEED*2)-MAX_SPEED,
						rand.nextInt(MAX_SPEED*2)-MAX_SPEED,
						rand.nextInt(20)+4,
						new Color(rand.nextInt(256*256*256)));
				balls.add(ball);
				add(ball);
			}
			n += m;
		}
	}
	
	/**
	 * Klasse für die Bälle
	 */
	private static class Ball extends JComponent {

		private float x, y;
		private float vx, vy;
		private int size;
		private Color col;
		
		public Ball(int x, int y, float vx, float vy, int size, Color col) {
			this.x = x;
			this.y = y;
			this.vx = vx;
			this.vy = vy;
			this.size = size;
			this.col = col;
			setLocation(x, y);
			setSize(size,size);
		}
		
		/**
		 * Bewege dich soweit, wie du in dt kommst und gibt die benötigte Zeit
		 * für setLocation zurück
		 * @param dt - wie viele Millisekunden der Ball gekommen ist
		 * @return - Zeit für den setLocation Aufruf
		 */
		private long step(long dt) {
			x = x+vx*dt/1000;
			y = y+vy*dt/1000;
			
			int xx = (int) Math.round(x);
			int yy = (int) Math.round(y);
			// meassure time for setLocation
			long t0 = System.nanoTime();
			setLocation(xx,yy);
			return System.nanoTime()-t0; // time for setLocation
		}
		
		public void paintComponent(Graphics g) {
			g.setColor(col);
			g.fillOval(0,0,size,size);
		}
	}
}
Das KSKB ist leider etwas gross geworden.

Das Applet kann man auch hier im Browser starten oder downloaden und als Applikation (jar) starten. TimerApplet Informationen und Download
Hier wird auch noch die Zeit, die für das Zeichnen benötigt wird angezeigt und man kann Bälle hinzufügen oder entfernen.

Wieso bringt ein Panel in einem JApplet nicht dieselbe Leistung, wie in einem JFrame?
 
Viele Ideen scheint es leider nicht zu geben. Oder ist das KSKB zu gross/kompliziert? Musste leider etwas gross sein, weil der Effekt bei wenigen Bewegungen nicht zu sehen ist. Ich finde jedenfalls, es sieht cool aus, es sich anzusehen ist es allemal wert 😉

Ich habe selbst noch etwas nachgeforscht, aber nicht mehr viel herausgefunden. Weil setLocation sowieso setBounds(x,y,widht,height) aufruft, habe ich es gleich dadurch ersetzt, der Effekt bleibt der Gleiche. Dann habe ich einige Methoden, die setBounds benutzt analysiert, z.B. getTreeLock(), konnte bei denen aber kein anderes Verhalten für Applets und Frames finden. Übrig geblieben sind noch einige, die nicht sichtbar sind, deren Zeitbedarf ich also nicht genau messen kann und an einer solchen muss es wohl liege 🙁
Es könnten mixOnReshaping(), reshapeNativePeer(…), notifyNewBounds(…) oder repaintParentIfNeeded(…) sein.

Ich fines es jedenfalls seltsam.
 
Wahrscheinlich als JFrame oder? Dann hast du einen schnellen Rechner 😀
Aber wenn du es als JApplet startest, ist es wahrscheinlich langsamer.
 
zu meiner schande muss ich zugeben das ich in meinem javabuch noch nicht so weit bin als das ich mit dem begriff japplet was anfangen könnte :/ sry
 
Hmja, der Unterschied ist schon ziemlich deutlich. (Abgesehen davon, dass ich - wie schon in anderen Threads zur genüge breitgetreten habe - nicht viel davon halte, einen Sprite von "JComponent" erben zu lassen - und der Unterschied, der entsteht, wenn man Ball mal NICHT von JComponent erben läßt, und die Gesamtzeit von TimerPanel#step bei 10000 Bällen mißt, ist ein weiterer Grund 😉 ).

Die Ursache... naja ... ist schwer zu sagen.... setLocation landet irgendwann bei "Component#reshape...", man könnte mal schauen, wo von dort aus Applets anders behandelt werden als JFrames....
 
Mit der breitgetretenen Diskussion meinst wahrscheinlich diesesn Thread hier http://www.java-forum.org/awt-swing-swt/75096-schichten-jpanel.html ^^

Die Sprites habe ich von JComponent erben lassen, weil es einen gewissen Komfort bringt, konnta ja nicht ahnen, dass dass setLocation()/move()/setBounds()/reshape() dann so lange dauert. Naja, jetzt wissen wir es 🙂
Aber eine simple Erklärung/Lösung gibt es in dem Fall nicht.

Habe es jetzt umgeschrieben, ohne JComponent, das war in diesem Beispiel jetzt auch ganz einfach. Die Methode step läuft nun in JFrames und Applets sehr schnell ab, meistens unter einer ms, was natürlich hervorragend ist.

Vielen Dank für alle Antworten
 

Zurück
Oben