Merkwürdiges Verhalten zeichnen sperater Thread

FunMaker

Mitglied
Hallihallo,

Ich habe ein sehr merkwürdiges Problem wo ich echt nicht weiß wie das zustande kommt.
Ich habe eine Simulation die im Main Thread abgearbeitet wird. Dann habe ich zusätzlich ein JFrame dessen BufferStrategy ich an meinen Zeichenthread weitergebe. Der Zeichenthread ist nun alleine für das zeichnen der Simulationsergebnisse verantwortlich - das Zeichnen über den AWT Thread habe ich auf Grund schlechter Kontrollmöglichkeiten ausgeschlossen.

Hier nun die run Methode des Zeichen Threads:
Java:
public void run()
{
int timeCounter = _simulation.getTimeCounter();
int counter = 0;
		
while (!this.interrupted())
{
//HIER WIRD GEZEICHNET
//...
//<-
		

//******
//int x ;
//******

while (timeCounter == _simulation.getTimeCounter())
{
counter ++;
System.out.println(counter);
	
try
{
Thread.sleep(1);
}
catch (Exception e)
{
}
}
timeCounter = _simulation.getTimeCounter();
}
}

Die interne while Schleife soll dabei erzwingen das nur einmal pro Iterationsschritt gezeichnet wird (weil öfters wäre unnütz)

Soweit so gut. Funktioniert auch alles wunderbar. Der Witz ist:

wenn ich das
Java:
int x;
einkommentiere gibt es einen sehr großen Unterschied in der Zeichengeschwindigkeit. x wird dabei nirgendwo sonst verwendet, weder gelesen noch geschrieben.

mit der int Variable: er kommt in die interne while Schleife und spult diese so lange ab bis zur nächsten Simulationsiteration - dann zeichnet er und das ganze von vorne. Die Darstellungsgeschwindigkeit ist hierbei recht angenehm. Und da er so ziemlich in jedem Iterationsschritt in die While Schleife kommt gehe ich davon aus das er für das Zeichnen weniger Zeit braucht als für die Simulation

ohne die int Variable: Er geht niemals in die interne while Schleife hinein (nur ganz am Anfang als die Simulation noch am initiieren ist) - später aber garnicht mehr und ist die ganze Zeit am zeichnen. das Zeichnen ist nicht besonders angenehm mit anzusehen da er so auch Bilder aus mehreren Simulationsschritten in einer Darstellung vereint (was aber grundsätzlich nicht so schlimm ist) - das Hauptproblem ist halt das das Zeichnen sehr unangenehm langsam wird.

Wenn ich jetzt den sleep Befehl aus der While Schleife entferne dann ist es auch ohne int Variable vernünftig lauffähig nur ist dann die Auslastung der CPU bei weitem höher weil sie ja nicht IDLE läuft sondern die ganze Zeit NICHTS tut - verwunderlich da er ja ohne die int Variable eigentlich erst garnicht in die While Schleife reingeht und somit auch das entfernen des Sleeps nichts ändern sollte.

Es findet sonst keine Synchronisation mit dem Simulationsthread statt - dies ist so beabsichtigt falls die Frage aufkommt.

Irgendwer ne Idee woran das liegen könnte? Weißt echt nicht woran das liegen könnte und die (ansich sinnlose) Int Variable möchte ich auch nicht drinne lassen geschweige denn das Sleep entfernen.

Als Hinweis: CPU i7 920 - also vernünftig für Multithreaded. Läuft auf Win XP
 
wie genau zeichnest du denn???
Du kannst NUR im EDT zeichnen... Das heißt du musst die Zeichenvorgänge synchronisieren!
Zweitens macht dir eine eigene boolean Variable anstatt dem interuppted!!!
 
Zuletzt bearbeitet von einem Moderator:
In einen eigenen Buffer (BufferedImage) kann man afaik auch von einem anderen Thread aus zeichnen, aber ... insgesamt klingt das ziemlich schwammig... ein bißchen genauere Infos wären nicht schlecht...
 
wie genau zeichnest du denn???
Du kannst NUR im EDT zeichnen... Das heißt du musst die Zeichenvorgänge synchronisieren!
Zweitens macht dir eine eigene boolean Variable anstatt dem interuppted!!!

Das ist in sofern richtig das der EDT Thread letztendlich für das darstellen des gezeichneten Bildes zuständig ist, aber das tatsächliche Zeichnen des Bildes muss nicht dort stattfinden.


Der komplette Zeichencode.

Java:
	public void run()
	{

		int timeCounter = _simulation.getTimeCounter();
		int counter = 0;
		
		while (!interrupted())
		{

			// The food is painted
			_renderTarget.setPaint(Color.blue);
			for (int i = 0; i < _simulation.fieldArray.length; i++)
			{
				for (int j = 0; j < _simulation.fieldArray[0].length; j++)
				{
					if (_simulation.fieldArray[i][j] != null && _simulation.fieldArray[i][j].getFoodCount() > 0)
					{
						Field field = _simulation.fieldArray[i][j];

						_renderTarget.fillRect(Math.round((((field.getX()) - getCenterX() + _random.nextInt(_simulation.fungusWidth)) * getZoom()) + getHalfWidth()),
											   Math.round((((field.getY()) - getCenterY() + _random.nextInt(_simulation.fungusHeight)) * getZoom()) + getHalfHeight()),
											   1, 1);
					}
				}
			}

			// The Light is painted
			for (int i = 0; i < _simulation.lightVector.size(); i++)
			{
				_renderTarget.setPaint(Color.yellow);
				_renderTarget.fillRect(Math.round((_simulation.lightVector.get(i).getX() - getCenterX()) * getZoom()) + getHalfWidth(),
									   Math.round((_simulation.lightVector.get(i).getY() - getCenterY()) * getZoom()) + getHalfHeight(),
									   Math.round((_simulation.lightVector.get(i).getWidth()) * getZoom()),
									   Math.round((_simulation.lightVector.get(i).getHeight()) * getZoom()));
			}
			
			Vector<Fungus> targetVector = _simulation.fungiVector;

			for (int i = 0; i < targetVector.size(); i++)
			{

				Fungus fungus = targetVector.get(i);

				Point fungusPoint = _simulation.getCurrentCampField(fungus.getMiddleX(),fungus.getMiddleY());
				if (pointinRect(fungus.getMiddleX(), fungus.getMiddleY(), getTransformedLeft(), getTransformedTop(), getTransformedRight(), getTransformedBottom())
					&& (_simulation.fieldArray3Dim[fungusPoint.x][fungusPoint.y][fungus.getEtage()+1].getFungus() == null))
				{
				
					getRenderTarget().drawImage(_sprites[fungus.getDisplayType()],
										Math.round(((fungus.getX() - getCenterX()) * getZoom())) + getHalfWidth(), 
										Math.round(((fungus.getY() - getCenterY()) * getZoom())) + getHalfHeight,
										Math.round(fungus.getWidth() * getZoom()),
										Math.round(fungus.getHeight() * getZoom()), null);

				}
			}
			
			if(_simulation.paintItBlack == true){
				

				getRenderTarget().setPaint(Color.black);
				getRenderTarget().fill(new Rectangle2D.Double(0, 0, getWidth(),getHeight()));
				_simulation.paintItBlack = false;
			}				
			
			updatePresentParameters();

			// Wir zeichnen erst neu wenn sich tatsächlich was am
			// Simulationsmaterial geändert hat
		
			//int x ;
						
			while (timeCounter == _simulation.getTimeCounter())
			{
				try
				{
					Thread.sleep(1);
				}
				catch (Exception e)
				{

				}
			}
			timeCounter = _simulation.getTimeCounter();
		}
	}

	public void updatePresentParameters()
	{
		updateDimensions(_updateCenterX, _updateCenterY, _updateWidth, _updateHeight, _updateZoom);
		flipBuffers();
		getRenderTarget().setColor(Color.WHITE);
		getRenderTarget().fillRect(0, 0, _width, _height);
	}

	private void flipBuffers()
	{
		_frameBuffer.show();
		_renderTarget = (Graphics2D) _frameBuffer.getDrawGraphics();
		_renderTarget.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
	}

	private boolean updateDimensions(int centerX, int centerY, int width,
			int height, float zoom)
	{
		// gibt true zurück wenn sich die Maßendes Bildes geändert haben
		boolean result = ((getWidth() != width) || (getHeight() != height));
		_centerX = centerX;
		_centerY = centerY;
		_width = width;
		_height = height;
		_zoom = zoom;

		_transformedLeft = _centerX - Math.round(width * _zoom) / 2;
		_transformedRight = _centerX + Math.round(width * _zoom) / 2;
		_transformedTop = _centerY - Math.round(height * _zoom) / 2;
		_transformedBottom = _centerY + Math.round(height * _zoom) / 2;

		_halfWidth = width / 2;
		_halfHeight = height / 2;
		//_halfZoom = _zoom * 0.5f;

		return result;

	}

D.h. im Grunde zeichne ich nur mit FillRect und DrawImage auf dem Graphics2D Objekt. Die Sprites die per DrawImage gezeichnet werden sind alle BufferedImages

Der _frameBuffer kommt wie gesagt vom JFrame
 
wie gesagt mach eine eigene boolean Variable für interrupt...
Warum machst du 2 Schleifen? Wenn du willst dass nur einmal gezeichnet wird dann mach das doch außerhalb deiner Schleifen oder setz ein boolean flag in die Methode, aber dass durch eine interne while Schleife zu lösen... naja...

EDIT: was ist das render Target?
 
Warum kann die Simulation nicht melden, dass ein Simulationsschritt abgearbeitet wurde und etwas neu gezeichnet werden kann.
Mir scheint da ein konzeptioneller Fehler vorzuliegen.

Und diese while Schleife erzeugt nur unnötig Prozessorlast:
Java:
			while (timeCounter == _simulation.getTimeCounter())
			{
				try
				{
					Thread.sleep(1);
				}
				catch (Exception e)
				{

				}
			}
 
Warum kann die Simulation nicht melden, dass ein Simulationsschritt abgearbeitet wurde und etwas neu gezeichnet werden kann.
Mir scheint da ein konzeptioneller Fehler vorzuliegen.

Und diese while Schleife erzeugt nur unnötig Prozessorlast:

Ich möchte eine möglichst strikte Trennung zwischen View und Modell, d.h. die Simulation weiß garnicht das sie gezeichnet wird.

wenn ich sie komplett weglasse ist die Last bei weitem höher da er dann dauerhaft am zeichnen ist (gesetzt den Fall mit der Integer Variablen) - was aber unnötig ist da sich ja in der Zwischenzeit nichts ändert.
 
Ich möchte eine möglichst strikte Trennung zwischen View und Modell, d.h. die Simulation weiß garnicht das sie gezeichnet wird.
Das muss sie auch nicht. Sie muss einfach nur zulassen, dass Objekte die ein bestimmtes Interface implementieren sich als Listener/Observer an dem Simulationsobjekt registieren können. Dann kann die Simulation den Objekten einfach nur mitteilen: Hallo bei mir hat sich was verändert. Was die damit mit der Info machen, bleibt denen überlassen (siehe Entwurfsmuster MVC oder Observer)
 
Man könnte auch die Simulationsdaten/ergebnisse als eigenständiges Model herauslösen, welches dann von View und Simulation gemeinsam genutzt wird.
 
Ich hatte es kurz überlegt das ganze dem Observer Pattern entsprechend zu machen - habe mich dann aber für den ganz harten Weg entschieden das die Simulation keinen zusätzliche Overhead auf ihrer Seite hat (ausserdem macht ein richtiges Observer Pattern meiner Meinung nach nur Sinn wenn es mehrere Observer gibt oder der Status des Modells mehrere millionen mal abgefragt werden würde - bei einem einzigen Observer ist es auch nicht so wild wenn derjenige selber guckt ob es was neues gibt)

Aber davon mal abgesehen wie man nun Modell und View trennt oder auch nicht, bleibt für mich trotzdem die Frage warum das Verhalten durch den offensichtlich unnötigen Einschub der Integer Variable sich so stark verändert?
 
Aber davon mal abgesehen wie man nun Modell und View trennt oder auch nicht, bleibt für mich trotzdem die Frage warum das Verhalten durch den offensichtlich unnötigen Einschub der Integer Variable sich so stark verändert?
Keine Ahnung was da intern passiert. Ich hätte erwartet, dass sowas beim kompilieren wegoptimiert wird. Ist das Verhalten in einem KSKB darstellbar? Dann könnten wir uns das genauer anschauen.

Zum Thema Observer: Wenn das Model die View nicht kennt, ist das für mich ein vernünftiger Weg - unabhängig davon wie viele Observer es letztendlich geben wird. Und der Overhead ist bei den paar Zeilen zusätzlichem Code überschaubar.
Zumal das ständige aktive "Nachschauen" durch die View, ob sich am Model was geändert hat - vor allem in den kurzen Zeitabständen von 1ms - doch recht fragwürdig ist.
 
Probier doch einfach die Vorschläge mal aus und mach deine while Schleife raus...

wenn ich die While Schleife einfach entferne brauche ich wie oben gesagt auch keine integer Variable - dafür läuft der Thread halt dauernd was ich aber nicht möchte (die dadurch freiwerdende Leistung kann man ja noch wo anders einsetzen) - die Last des Kerns wodrauf der Thread läuft ist in dem Fall wo ich nicht warte bei 100% - im anderen bei 60-70% Vorallem bei Single Core CPUs wäre das ganze dann doch interessant. Vielleicht meinst du es aber auch ganz anders und ich versteh Dich nur falsch 🙂

Keine Ahnung was da intern passiert. Ich hätte erwartet, dass sowas beim kompilieren wegoptimiert wird. Ist das Verhalten in einem KSKB darstellbar? Dann könnten wir uns das genauer anschauen.

Zum Thema Observer: Wenn das Model die View nicht kennt, ist das für mich ein vernünftiger Weg - unabhängig davon wie viele Observer es letztendlich geben wird. Und der Overhead ist bei den paar Zeilen zusätzlichem Code überschaubar.
Zumal das ständige aktive "Nachschauen" durch die View, ob sich am Model was geändert hat - vor allem in den kurzen Zeitabständen von 1ms - doch recht fragwürdig ist.

Ok - habe die Abfrage der Simulation jetzt durch ne boolean ersetzt der halt gesetzt wird wenn die Simulation mit einer Iteration fertig ist.

Als Ich habe es mal schnell in nem KSKB ausprobiert was aber wirklich aufs minimum reduziert ist (d.H. 2 Threads, einer tut irgendwas, der andere wartet nur dadrauf das er fertig ist und tut dann seinerseits wieder etwas...) - kein Unterschied - ich werde mal gucken ob das ganze auf nem anderen Rechner den gleichen Effekt hat - vllt hat es ja doch was mit der Plattform zu tun.
 
Ich habe denke ich die Lösung gefunden...

Als Hintergrund: Die Simulation ist ursprünglich eine Masterarbeit zur Simulation von biologischen Systemen gewesen. Da hierbei viele mehrdimensionale Arrays mit Objekten und großen Indizes verwendet werden musste die JVM mit größerem Heap Speicher gestartet werden (parameter -Xmx512M). Nun habe ich von Java 6 Update 17 auf Update 20 geupdated und das Problem war noch schlimmer als vorher. Nach ein bisschen rumprobieren musste ich feststellen das der Speicher für die Simulation insgesamt auch mit der Erweiterung sehr knapp bemessen war. also den Speicher auf 1GB erhöht und schwubbs waren alle Probleme weg.
Meine Vermutung ist nun dass der Speicher ohne die Int Variable irgendwann voll wurde und so im ungünstigen Moment der GC in Aktion treten musste um Speicher frei zu machen. Durch die Int Variable würde ich nun vermuten das er häufig ungenutzen Speicher frei gemacht hat und es so nie zu einem kritischen Engpass gekommen ist. Auf jeden Fall hat das erhöhen des Speichers das Problem gelößt 🙂

Vielen dank an alle die sich des Problems angenommen haben 🙂
 

Zurück
Oben