# SWT Performance : "Text" - Ausgabe beschleunigen ?



## pocketom (24. Mai 2007)

Hallo,
ich habe eine kleine Anwendung die überwiegend verschiedene Dinge in einem großen Textfenster ausgibt. Hauptsächlich werden dort Sachen aufgelistet, die Daten dazu werden aus einem Vektorcontainer gelesen. Oftmals wird dabei auf mehr als 500 Datenobjekte zugegriffen, es entstehen i.d.R. Logs in Tabellenform. Meine Ausgabe hatte ich bis jetzt ganz simpel in der Console nach dem Schema: 

```
while(myiterator.hasnext()) 
     System.out.println(myiterator.next().getMyText());
```
Das geht auf der Konsole eigentlich sehr flott, der Zugriff auf den Datencontainer an sich ist also offenbar schnell genug(sicher kann man statt Vector auch hier noch was schnelleres nehmen...).


Nun mache ich im Prinzip genau das gleiche in meiner kleinen SWT Gui Anwendung:

```
Text logwindow = new Text(shell, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);

while(myiterator.hasnext()) 
     logwindow.append(myiterator.next().getMyText()+"\n");
```

Das ganze dauert ewig, vor allem sieht man während der Verarbeitung keinen Fortschritt obwohl ich "append" benutze. Das Ergebnis, also z.B. 500 Zeilen "Bla Bla", wird am Ende auf einen Schlag angezeigt anstatt es zeilenweise aufzubauen. In der Konsole hingegen wird während die while Schleife noch läuft wird immer sofort die Ausgabe der jeweiligen Zeile gemacht. 

Wie krieg ich das in meinem SWT Fenster hin, zumindest so das der Benutzer zusehen kann während sein log erstellt wird und man nicht nach ner halben Minute denkt das Programm wäre abgestürzt...?


----------



## byte (24. Mai 2007)

Du solltest Berechnungen nicht im Event-Dispatching-Thread der GUI machen, weil das GUI sonst in der Zeit blockiert. Pack die Iteration also in einen eigenen Thread und kapsel die Zugriffe auf die GUI dann in ein Runnable und übergib dies an die Display#asyncExec(). Das ist nötig, weil SWT es verbietet, von einem anderen Thread schreibend auf die GUI zuzugreifen. Also in etwa so:



```
display.asyncExec(new Runnable() {
  public void run() {
    text.append(...);
  }
});
```


----------



## Guest (24. Mai 2007)

Alles klar, danke! Ich probiers morgen gleich mal aus und meld mich nochmal wenns Probleme gibt. Bis jetzt habe ich es so das die Iterationen in einer eigenen Printfunktion laufen der man die Referenz zum Textfeld mitgeben kann(void printOutput(Text texbox))

Das würde ich also jetzt so machen?:


```
class myDataClass{


    ...
    ...
    ...

    public void printOutput(Display display, Text texbox)
    {

            ...
            ...
            ...

            display.asyncExec(new Runnable() { 
                  public void run() { 

                        while(myiterator.hasnext()) 
                             textbox.append(myiterator.next().getMyText()+"\n");
                 } 
            });
    }
}

 :?:
```


----------



## Wildcard (24. Mai 2007)

```
textbox.append(myiterator.next().getMyText()+"\n");
```
Das ist deine Performancekrücke.
Nimm einen StringBuilder.


----------



## pocketom (25. Mai 2007)

Was mich am "display.asyncExec(new Runnable()....." stört ist das ich meine Variablen die ich an die Funktion übergebe alle im Funktionskopf final machen muss. Sämtliche Berechnungen muss ich dann ausserhalb meiner Funktion machen, was mir die Kapselung im Prinzip zunichte macht... Kann man das nicht noch irgendwie anders lösen?


----------



## Wildcard (25. Mai 2007)

Indem du keine anonymen inneren Klassen benutzt.
Hast du den StringBuilder eingebaut?


----------



## pocketom (25. Mai 2007)

Hi, den StringBuilder hab ich eingebaut und verwende ihn analog zu meiner Textbox:


```
StringBuilder strb = new StringBuilder();

...
...

while(myiterator.hasnext())
                             strb.append(myiterator.next().getMyText()+"\n");
```


Wie mache ich dass das ich keine anonyme innere Klasse benutze? Ich habe alles mögliche probiert, aber diese Konzepte kenne ich irgendwie nicht. :bahnhof: 


```
public void myFunction(Display display, int myValue){
		display.asyncExec(new Runnable() {
                     public void run() {
                                 myValue++;               
           }
      });
```
kannst du mir das vielleicht anhand meines kleinen Beispiels kurz erklären? Mir fehlt irgendwie der entscheidende Denkanstoß  ???:L


----------



## Wildcard (25. Mai 2007)

```
strb.append(myiterator.next().getMyText()+"\n");
```
Der gleiche Mist. In einer Schleife die häufig durchlaufen wird *darf* es kein String + String geben.
Was für ein Beispiel denn? Du wirst doch wissen wie man eine Klasse schreibt die Runnable implementiert?  ???:L


----------



## pocketom (29. Mai 2007)

Hi. Ich verstehe nicht wie ich es sonst machen soll. Die Schleife wird nunmal häufig durchlaufen, trotzdem muss alles in der Textbox ausgegeben werden, ich versteh nicht ich es sonst machen soll...  :bahnhof:

Muss ich jetzt für alles was ich in meiner Textbox ausgeben will eine neue Klasse schreiben die Runable implementiert???


----------



## pocketom (30. Mai 2007)

Sorry das ich nochmal fragen muss, aber es ist dringend und ich komme leider auf keine bessere Lösung



> In einer Schleife die häufig durchlaufen wird darf es kein String + String geben.



Wie umgehe ich das Problem in meinem Fall am geschicktesten? Ich habe wiegesagt keine Möglichkeit die Schleife abzuschaffen, ich brauche von einigen hundert Objekten je eine Zeile Ausgabe, so dass am Ende im Textfeld auch einige hundert Zeilen stehen. Wie ich das nicht in einer Schleife hinkriegen soll ist mir ein Rätsel, ich muss schliesslich alle Objekte durchgehen. Das während dem Schleifendurchlauf in den Stringbuilder zu stecken erfordert ja auch ein "append" o.Ä., also was soll ich machen   ???:L  :bahnhof:  :cry:


----------



## byte (30. Mai 2007)

Einfach zweimal appenden statt + Operator:

```
strb.append(myiterator.next().getMyText());
strb.append("\n");
```



> Was mich am "display.asyncExec(new Runnable()....." stört ist das ich meine Variablen die ich an die Funktion übergebe alle im Funktionskopf final machen muss. Sämtliche Berechnungen muss ich dann ausserhalb meiner Funktion machen, was mir die Kapselung im Prinzip zunichte macht... Kann man das nicht noch irgendwie anders lösen?



Deine Kapselung ist eh etwas komisch, wenn ich mir den Code weiter oben angucke. Mach den asyncExec() Aufruf nicht in diese Function, sondern dort, wo die GUI aktualisiert wird. Und vergiß nicht, dass ganze aus einem neuen Thread aufzurufen, sonst hast Du nichts gewonnen. Also Deine Schleife muss in einem eigenen Thread laufen. Und nur die Zeile, wo Du aus diesem Thread Deine GUI aktualisierst, muss über asyncExec() laufen.


----------



## pocketom (30. Mai 2007)

> Einfach zweimal appenden statt + Operator:


Argh, sorry da war ich wohl blind. Da muss man erstmal drauf kommen. Ich dachte bis jetzt das "+" eh das selbe tut wie "append" ...



> Und vergiß nicht, dass ganze aus einem neuen Thread aufzurufen, sonst hast Du nichts gewonnen. Also Deine Schleife muss in einem eigenen Thread laufen.



Das mit dem asyncExec und den Threads versteh ich leider immer noch nicht so 100%. Ich habe noch nie damit gearbeitet. Weist du vielleicht einen Link wo man sich das mal an einem kleinen Beispiel ansehen kann? Zu Threads hab ich zwar tausende gefunden, aber wie ich das jetzt bei mir am geschicktesten mit dem asyncExec, dem Stringbuilder und meiner GUI kombiniere hab ich immer noch ned so raus. Irgendwie hab ich wohl ein bischen den Überblick verloren. Aber ich programmiere ja auch erst seit zwei monaten Java...   :lol:


----------



## byte (30. Mai 2007)

Die SWT GUI läuft in einem Thread, der auch die Events verarbeitet (Event Dispatcher), also z.B. das Verändern der Fenstergröße oder das Drücken eines Knopfes. Nun lässt Du rechenintensive Berechnungen ebenfalls in diesem Thread ablaufen (in Deinem Fall wohl die Schleife mit dem StringBuilder). Diese Berechnungen blockieren nun die GUI, so dass es zu Verzögerungen beim Event-Dispatching kommt. So wird z.B. die GUI nicht richtig aktualisiert, weil einfach nicht genug Rechenzeit zur Verfügung steht.

Die Lösung: Rechenintensive Operationen gehören nicht in den Event-Dispatch-Thread, sondern in einen eigenen Thread. Das heisst, Du musst zunächst erstmal einen neuen Thread erzeugen und dort Deine Schleife machen (wie man mit Threads umgeht, ist in einem beliebigen Java-Grundlagenbuch nachzulesen).

Nun möchtest Du natürlich auch Deine GUI mit den berechneten Daten aktualisieren. Da wirft SWT eine Exception, wenn Du dies über einen anderen Thread als den Event-Dispatcher versuchst. Warum? Weil es so zu Race-Conditions und inkonsistenten Verhalten kommen kann, wenn man nebenläufig schreibend auf GUI Komponenten zugreift.

Lösung: Display.asyncExce() oder Display.syncExec() verwenden. Diese Methoden übergeben die Anfrage an den Event-Dispatcher, der sie nacheinander ausführt. Die Methoden verlangen ein Runnable, die man ohne weiteres als anonyme Objekte implementieren kann.

Wenn Du also aus einem zweiten Thread dem Text-Widget etwas hinzufügen willst, dann musst Du das in etwa so machen:


```
// irgendwo in einem anderen Thread
// statt 'text.append("hallo");' nun einfach folgendes:
Display.asyncExec(new Runnable() {
  public void run() {
    text.append("hallo");
  }
});
```


----------



## pocketom (30. Mai 2007)

Super, klasse und vielen Dank! Jetzt wird mir das langsam klrer. Denke ich werds jetzt hinbekommen. Vielen Dank nochmal!


----------



## pocketom (31. Mai 2007)

Hi, das mit der Performance haut mittlerweile ganz gut hin, der Stringbuilder hat das ganze um ein vielfaches beschleunigt. Allerdings klappts mit dem Echtzeitoutput im Textfeld immer noch nicht, die Anfrage wird erst nachdem sie bearbeitet wurde auf einen Schlag angezeigt, und nicht so wie gewünscht schon während der Berechnung parallel aktualisiert (trotz display.asyncExec() )

Ich bin wie folgt vorgegangen:



```
public class PrintString implements Runnable {
	
	Display display;
	Text textbox;

	
	// Constructor
	public PrintString(Display display, Text textbox)
	{
		super();
		this.display = display;
		this.textbox = textbox;
				
		this.run();
	}	
	
	public synchronized void run()
	{			
		final StringBuilder strb = new StringBuilder();
		
		...
		...
		...
		
			
		while(readings_iterator.hasNext())
		{
			// fetch current object into temporary read-object
			temp = (SingleRead) readings_iterator.next();
							
			strb.append("Sequence:				");
			for(int i=0;i<temp.getHeader().getNumber_of_bases(regfile.getFileHeader().getKey_length(),taglength);i++)
			{
				strb.append(temp.getData().getBase(i, regfile.getFileHeader().getKey_length(), taglength,  false, false));
				strb.append("\t");
			}
			strb.append("\n");			
				
			
			// send result to textbox immediately
			// clear the stringbuilder
			display.asyncExec(new Runnable() {
					  public void run() {
						  textbox.append(strb.toString());
						  strb.delete(0, strb.length());
					  }
				});				
		}			
	}		
}
```
geht wiegeagt halbwegs flott, aber ich hätte jetzt erwartet das in jedem Schleifendurchlauf der gerade generierte String im Textfeld ausgegeben wird ? Mache ich noch irgendwas falsch?


----------



## byte (31. Mai 2007)

Hm komisch. Habs in ähnlicher Form in einem Projekt und da gehts einwandfrei. Hast Du es mal mit Display.syncExec() probiert? Oder mal explizit textbox.update() machen nach dem Append.


----------



## pocketom (31. Mai 2007)

Wenn ich "Display" groß schreibe bringt Eclipse den Fehler:
Cannot make a static reference to the non-static method asyncExec(Runnable) from the type Display

textbox.update() habe ich mit in die asynExec reingepackt, hat aber leider auch null Effekt  :bahnhof:


----------



## byte (31. Mai 2007)

Ändere mal asyncExec(...) in syncExec(...) in Deinem Code. Ansonsten fällt mir auch nix mehr ein. Bei mir gehts wie gesagt einwandfrei.


----------



## pocketom (31. Mai 2007)

bringt auch nix  :cry:  Müssen vielleicht alle meine Klassen eigene Threads sein?


----------



## byte (31. Mai 2007)

Hm, sehr merkwürdig. Du hast nicht zufällig irgendwo bei der Textbox setRedraw(false) gesetzt?

Du könntest am Anfang oder Ende der While-Schleife mal ein yield() einfügen. Kann mir aber kaum vorstellen, dass das was ändert. Ansonsten poste nochmal den aktuellen Code. Am besten auch die Stelle, wo Du das Text-Widget erzeugst.


----------



## pocketom (31. Mai 2007)

Hi, hab das ganze im Moment wieder rausgeschmissen. Ich werde es bei Gelegenheit mal an einem kleinen beispiel nochmal probieren. Der Code ist schon recht mächtig, und das immer so zusammenzuschneiden damits hier reinpasst macht mir zuviel Arbeit. Ich denke an einem kleinen Minibeispiel kommt das durchschaubarere. Ich poste es auf jeden Fall sobald ich es habe, obs dann geth oder nicht...


----------



## pocketom (1. Jun 2007)

So, seit heute habe ich "Text" gegen eine textbox vom Typ "StyledText" ausgetauscht. Seit dem funkt das mit der Live-Aktualisierung des Textes  :autsch: 

Da die Performance jetzt leider wieder verdammt mau ist, habe ich das mal in diesem Thread weitergeführt -> 
http://www.java-forum.org/de/topic50112_swt-styledtext-performance-steigern.html


----------

