|
Dakuan
Anmeldungsdatum: 2. November 2004
Beiträge: 6596
Wohnort: Hamburg
|
Ich benötige mal wieder einige Denkanstöße von den Profis. Also ich experimentiere immer noch mit digitaler Signalbearbeitung und möchte einige der Ergebnisse in Echtzeit grafisch darstellen. Dabei habe ich fest gestellt, dass die Berechnung der Daten nicht das eigentliche Problem ist. Der Flaschenhals ist die grafische Ausgabe. Momentan habe ich 2 Objekte, die grafischen Output erzeugen. In der zukünftigen Version sollen diese in einem eigenen Thread laufen (was ich bisher bei GUIs noch nicht hatte und jetzt eine Umstrukturierung erfordert). Was ich jetzt suche, ist eine Möglichkeit zu erkennen, dass eine Ausgebe möglicherweise zu stark verzögert statt findet. In diesm Fall sollten die Bildschirm Ausgaben seltener aktualisiert werden, während die Berechnungen weiter laufen sollen. Mein FLTK Toolkit scheint mich dabei aber nicht unterstützen zu wollen. Ich kann nur für ein Objekt die Object->redraw() Methode aufrufen oder aus einem Tread Object->awake(Fl_Awake_Handler func) Damit kann ich nur hoffen, dass die Bildschirm Aktualisierung irgendwann mal erfolgt, kann aber nicht erkennen, ob das rechtzeitig vor dem Eintreffen neu zu bearbeitender Daten erledigt wurde. Wo kann ich da ansetzen oder welche Tricks könnten da helfen?
|
|
track
Anmeldungsdatum: 26. Juni 2008
Beiträge: 7174
Wohnort: Wolfen (S-A)
|
Ein grundsätzlicher Eckpunkt ist ja schon mal, dass man grundsätzlich höchstens alle 20ms die Ausgabe aktualisieren muss. Schneller ist nämlich die Bild-Wechselfrequenz gar nicht. (→ 50Hz, im Zweifel könnte man den genauen Wert bestimmt abfragen, oder sonst irgendwie darauf syncen) LG, track
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13365
|
track schrieb:
Schneller ist nämlich die Bild-Wechselfrequenz gar nicht. (→ 50Hz, im Zweifel könnte man den genauen Wert bestimmt abfragen, oder sonst irgendwie darauf syncen)
Ja, das müsste gehen (nur theoretisches Wissen hier). Man kann ja so vorgehen, dass man immer eine Version der darzustellenden Daten vorhält, die komplett berechnet ist und eine, auf der die Berechnung passiert. Wenn dann der Bildschirmsync auftritt, dann nimmt man halt, was immer gerade fertig ist und stellt es dar. Natürlich muss man dann ein bisschen die Threads synchronisieren, aber nur auf das Umsetzen der Zeiger auf die jeweiligen Inhalte. Und man muss sauber aufräumen.
|
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6596
Wohnort: Hamburg
|
Ja, ich habe auch schon festgestellt, das ich nicht so schnell gucken kann 😉 Momentan gehe ich von 40 ms aus. Aber das mit dem Synchronisieren wird wohl schwierig. Ich habe ja keinen Zugriff auf die Hardware oder auf das X-Window System (und auch keine Ahnung davon).
Man kann ja so vorgehen, dass man immer eine Version der darzustellenden Daten vorhält, die komplett berechnet ist und eine, auf der die Berechnung passiert.
Ich hatte mir auch schon überlegt, dafür 2 Puffer zu verwenden, die abwechselnd benutzt werden. Die Berechnung der Daten incl. grafische Aufbereitung dauert in dem einen Fall übrigens nur 1.8 ms (Abhängig von der Fenstergröße). Die meiste Zeit verbraucht die Bildschirmausgabe.
Natürlich muss man dann ein bisschen die Threads synchronisieren, ...
Das ist dann die nächste Aufgabe. So etwas hatte ich bisher nur einmal gemacht und dabei einen Ringpuffer verwendet. Hat zwar etwas gedauert bis das lief, aber vom Ergebnis war ich begeistert.
|
|
barcc
Anmeldungsdatum: 13. Juli 2007
Beiträge: 696
Wohnort: Dortmund
|
Wenn deine Berechnung länger als 40ms dauert, ist höchstwahrscheinlich in deiner Ausgaberoutine was faul. Während der Ausgabe soll die Grafikkarte arbeiten, nicht die CPU.
Ein Scenegraph kann dir dabei helfen (moderne GUI-Toolkits haben sowas an Bord, es gibt aber auch unabhängige). Wenn du OpenGL direkt verwenden willst, benutze nur modernes OpenGL. Wenn deine Daten tatsächlich so umfangreich sind, dass die Grafikkarte in die Knie geht, reduziere die Daten. (Wenn die Berechnung tatsächlich nur 1,8ms dauert, kann ich mir das nicht vorstellen)
|
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6596
Wohnort: Hamburg
|
Also die 40 ms sind das Zeitintervall, nachdem die aufgesammelten Daten grafisch dargestellt werden sollten, wenn es nicht zu ruckelig werden soll.
* Wenn deine Daten tatsächlich so umfangreich sind, dass die Grafikkarte in die Knie geht, reduziere die Daten. (Wenn die Berechnung tatsächlich nur 1,8ms dauert, kann ich mir das nicht vorstellen)
Es handelt sich hier um ein vereinfachtes Oszilloskop mit bis zu 8 Kanälen, das horizontal gescrollt wird und das hier bereits erwähnte Wasserfall Diagramm (auf welches sich auch die 1.8ms beziehen). Der vom Programm aufbereitete Pixel Puffer wird dann wie ein Bild mit auf den Bildschirm gebracht. Ob OpenGL das beschleunigen kann weiss ich nicht. Aber wie auch immer, ich denke dass ich irgendwie reagieren muss, wenn das Betriebssystem meint, mich mit zusätzlichen Aktivitäten ausbremsen zu müssen. Die Frage war ja nicht, wie ich die Bearbeitung beschleunigen kann, sondern ob ich erkennen kann das ich regieren muss. Eine Idee ist mir dazu gerade gekommen, wobei ich aber noch keine Ahnung habe, ob das funktionieren kann. Wenn alle Bildschirmausgaben im Hauptthread passieren (muss laut FLTK Manual so sein), geht dieser wahrscheinlich irgendwann in den Wartezustand, bis ein neues Event eintrifft. Das letzte, vor dem Wartezustand könnte dann sein, den Füllstand der Puffer zu überprüfen um daraus die Auslastung abzuleiten. Allerdings habe ich keine Ahnung, ob ich dann in den sog. Worker-Threads noch Audio oder File I/O machen kann. Laut FLTK Manual sollen diese nicht auf Events reagieren.
|
|
barcc
Anmeldungsdatum: 13. Juli 2007
Beiträge: 696
Wohnort: Dortmund
|
Dakuan schrieb: Also die 40 ms sind das Zeitintervall, nachdem die aufgesammelten Daten grafisch dargestellt werden sollten, wenn es nicht zu ruckelig werden soll.
* Wenn deine Daten tatsächlich so umfangreich sind, dass die Grafikkarte in die Knie geht, reduziere die Daten. (Wenn die Berechnung tatsächlich nur 1,8ms dauert, kann ich mir das nicht vorstellen)
Es handelt sich hier um ein vereinfachtes Oszilloskop mit bis zu 8 Kanälen, das horizontal gescrollt wird und das hier bereits erwähnte Wasserfall Diagramm (auf welches sich auch die 1.8ms beziehen).
Du brauchst 1.8ms, um den Pixelpuffer zu füllen, und länger um das Bild darzustellen?
Der vom Programm aufbereitete Pixel Puffer wird dann wie ein Bild mit auf den Bildschirm gebracht. Ob OpenGL das beschleunigen kann weiss ich nicht. Aber wie auch immer, ich denke dass ich irgendwie reagieren muss, wenn das Betriebssystem meint, mich mit zusätzlichen Aktivitäten ausbremsen zu müssen.
Das Betriebssystem bremst dich aus? Was meinst du damit?
Die Frage war ja nicht, wie ich die Bearbeitung beschleunigen kann, sondern ob ich erkennen kann das ich regieren muss.
Die Frage hab ich verstanden, mein Eindruck war nur, dass du das Problem an falscher Stelle zu lösen versuchst. Anstatt dein Performance-Problem zu lösen, versuchst du die Framerate dynamisch anzupassen. Das sollte eigentlich nicht notwendig sein, ich kann mich natürlich auch irren.
Eine Idee ist mir dazu gerade gekommen, wobei ich aber noch keine Ahnung habe, ob das funktionieren kann. Wenn alle Bildschirmausgaben im Hauptthread passieren (muss laut FLTK Manual so sein), geht dieser wahrscheinlich irgendwann in den Wartezustand, bis ein neues Event eintrifft. Das letzte, vor dem Wartezustand könnte dann sein, den Füllstand der Puffer zu überprüfen um daraus die Auslastung abzuleiten. Allerdings habe ich keine Ahnung, ob ich dann in den sog. Worker-Threads noch Audio oder File I/O machen kann. Laut FLTK Manual sollen diese nicht auf Events reagieren.
Vielleicht so:
Benutze einen Timer, der alle 20ms ein Bildschirmausgabe-Event auslöst In deinem Bildschirmausgabe-Event-Handler berechne die Zeitdifferenz zum letzten Bildschirmausgabe-Event Wenn die Zeitdifferenz das Timer-Intervall übersteigt erhöhe das Intervall des Timers
|
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6596
Wohnort: Hamburg
|
Du brauchst 1.8ms, um den Pixelpuffer zu füllen, und länger um das Bild darzustellen?
Ich kann nicht messen, wie viel Zeit für die Anzeige benötigt wird. Ich kann nur allgemein feststellen, dass die Anzeige den Rechner mehr belastet als das rechnen. Momentan sind das nur 2 Anzeigen, aber später sollen noch weitere Aufgaben dazukommen.
Die Frage hab ich verstanden, mein Eindruck war nur, dass du das Problem an falscher Stelle zu lösen versuchst.
Obwohl ich das Programm auf einem älteren PC erstelle, habe ich noch keine wirklichen Performance Probleme. Das könnte aber passieren, wenn das mal auf einem Raspi laufen soll. Auf meinem neuesten PC zeigt sich htop wenig beeindruckt. Wie ein Timer da helfen kann, muss ich mal überlegen. Das Ausgaberaster wird ja durch die Daten bestimmt. Da muss ich mal sehen, wie man das zusammenbringt. Also beim Wasserfalldiagramm sind das Vielfache von 128ms (vorgegeben durch die FFT) und beim Impulsdiagramm Vielfache von 5ms (veränderbar, aber 5ms sind gut zu rechnen).
|
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6596
Wohnort: Hamburg
|
Ich hatte gestern auf dem Treffen eines anderen Forums Gelegenheit einen Profi zu fragen. Das hat keine 2 Minuten gedauert. Er hat dann vorsichtshalber hoch einen Blick auf den Quelltext geworfen und vor dem ersten Bier war das Notebook wieder vom Tisch (nicht das da noch was passiert). Hier mal die Zusammenfassung (mit Einleitung):
Alle eigenen grafischen Objekte müssen von einem FLTK Objekt abgeleitet sein und haben eine virtuelle draw() Methode. Diese darf das Programm aber nicht selber aufrufen. Stattdessen wird der Wunsch zum neu zeichnen mit redraw() angemeldet. Wenn mein Programm jetzt ein grafisches Objekt zeichnen will, geschieht dies durch bereitstellen einer eigenen draw() Methode, die dann irgendwann von der GUI aufgerufen wird. Das Ende dieser draw() Methode ist dann der Punkt, wo fest steht, das die grafische Ausgabe erfolgt ist, was dann dem Objekt über ein Flag signalisiert werden kann, wobei der Zugriff auf das Flag noch über ein Mutex geregelt werden muss. | void WF_Scope::draw( void ) {
fl_draw_image( wf_buffer, xpos, ypos, width, height, bytes_ber_pixel, bytes_per_line );
// hier ist fertig
}
|
Das Objekt kann so selbst entscheiden, ob es neu gezeichnet werden möchte. Der Hauptthread bekommt davon nichts mit.
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13365
|
Dakuan schrieb:
Wenn mein Programm jetzt ein grafisches Objekt zeichnen will, geschieht dies durch bereitstellen einer eigenen draw() Methode, die dann irgendwann von der GUI aufgerufen wird.
Das ist m.E. der entscheidende Punkt: Du hast ja hier Änderungen im Modell, die ein Neuzeichnen nötig machen (entgegen einem Event, z.B. einem Mausklick oder einem Window-Resize). D.h., das Toolkit wird nicht von sich aus draw() aufrufen. Würdest Du dann mit der Änderungsfrequenz der Datenänderung redraw() aufrufen? Was passiert dann, wenn draw() Deiner Klasse länger als 20ms braucht? Vielleicht liegt es daran, dass ich nie so tief in GUI-Programmierung eingestiegen bin, aber mir ist die gesamte Lösung noch nicht klar.
|
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6596
Wohnort: Hamburg
|
Die Lösung ist auch noch nicht wirklich fertig. Ich muss meine Objekte jetzt komplett umbauen, weil mir die besonderen Einschränkungen im Zusammenhang mit Threads noch nicht klar waren.
D.h., das Toolkit wird nicht von sich aus draw() aufrufen.
Doch, aber nur bei Aktionen, von denen es weiß, also z.B. wenn aufgrund anderer Erwignisse Teile der GUI neu gezeichnet werden müssen (Kostruktor aufgerufen, resize, darüberliegendes Fenster gelöscht ...).
Was passiert dann, wenn draw() Deiner Klasse länger als 20ms braucht?
Dann habe ich erstmal Pech gehabt. Aber das Objekt bekommt regelmäßig Eingabedaten. Nehmen wir mal als Beispiel das Wasserfalldiagramm. Wenn da das 1024-te Sample in den Eingabepuffer kommt, kann die Auswertung (Fouriertransformation) beginnen und ein neues Bild erstellt werden. Wenn zu diesem Zeitpunkt (oder schon vor der FFT) festgestellt wird, dass das Erledigt-Flag von draw() noch nicht gesetzt wurde, hat das vorherige neu Zeichnen zu lange gedauert und es wird auf ein erneutes redraw() verzichtet, wobei die Daten aber erstmal weiter bearbeitet werden. Bei anderen Klassen könnte ich die Abfrage des Flags vom Füllstand des Eingabepuffers abhängig machen. Aber wie gesagt, ich muss dafür viel umbauen. FLTK bietet da absichtlich wenig Unterstützung damit man nicht an bestimmte Libs gebunden ist. Ein einfaches Beispiel mit pthread.h ist aber dabei. Mein Tippgeber hat mich auch darauf aufmerksam gemacht, das ich dann auch STL thread's benutzen könnte (ich hoffe das ich den Namen halbwegs richtig wiedergegeben habe). Aber erstmal muss ich meine gesamte Datenübergabe umbauen. Wegen der Feiertage schaffe ich das in diesem Jahr wohl nicht mehr.
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13365
|
Dakuan schrieb: Die Lösung ist auch noch nicht wirklich fertig. Ich muss meine Objekte jetzt komplett umbauen, weil mir die besonderen Einschränkungen im Zusammenhang mit Threads noch nicht klar waren.
Ja, aber Du musst doch ein Verständnis haben, wie die Lösung auf abstrakterer Ebene aussehen soll, bevor Du anfängst Klassen zu ändern. Auf das Verständnis zielte meine Frage. Ich bin weniger an den Details interessiert, als am groben ganzen und dem prinzipiellen Ablauf.
D.h., das Toolkit wird nicht von sich aus draw() aufrufen.
Doch, aber nur bei Aktionen, von denen es weiß, also z.B. wenn aufgrund anderer Erwignisse Teile der GUI neu gezeichnet werden müssen (Kostruktor aufgerufen, resize, darüberliegendes Fenster gelöscht ...).
Das ist ja exakt das, was ich schrieb.
Was passiert dann, wenn draw() Deiner Klasse länger als 20ms braucht?
Dann habe ich erstmal Pech gehabt. Aber das Objekt bekommt regelmäßig Eingabedaten. Nehmen wir mal als Beispiel das Wasserfalldiagramm. Wenn da das 1024-te Sample in den Eingabepuffer kommt, kann die Auswertung (Fouriertransformation) beginnen und ein neues Bild erstellt werden. Wenn zu diesem Zeitpunkt (oder schon vor der FFT) festgestellt wird, dass das Erledigt-Flag von draw() noch nicht gesetzt wurde, hat das vorherige neu Zeichnen zu lange gedauert und es wird auf ein erneutes redraw() verzichtet, wobei die Daten aber erstmal weiter bearbeitet werden.
Mit anderen Worten: Du sendest ein redraw() ab, nachdem die Bearbeitung der Daten fertig ist und nur, falls gerade kein draw() unterwegs ist. Habe ich das richtig verstanden?
|
|
Dakuan
(Themenstarter)
Anmeldungsdatum: 2. November 2004
Beiträge: 6596
Wohnort: Hamburg
|
Ja, aber Du musst doch ein Verständnis haben, wie die Lösung auf abstrakterer Ebene aussehen soll, bevor Du anfängst Klassen zu ändern. Auf das Verständnis zielte meine Frage.
Ich habe schon eine Vorstellung, wie das in etwa aussehen soll. Allerdings habe bisher nur Konsolenanwendungen mit Threads erstellt. Wie das mit GUI funktioniert, muss ich erst ausprobieren. Klar ist aber schon jetzt, das ich meine Klassen nicht so lassen kann. Die Daten Eingabe muss geändert werden.
Mit anderen Worten: Du sendest ein redraw() ab, nachdem die Bearbeitung der Daten fertig ist und nur, falls gerade kein draw() unterwegs ist. Habe ich das richtig verstanden?
Genau so will ich es machen.
|
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 13365
|
Dakuan schrieb:
Mit anderen Worten: Du sendest ein redraw() ab, nachdem die Bearbeitung der Daten fertig ist und nur, falls gerade kein draw() unterwegs ist. Habe ich das richtig verstanden?
Genau so will ich es machen.
Dange!
|