Zu viele Timer Events

Übersicht BlitzMax, BlitzMax NG Allgemein

Neue Antwort erstellen

Silver_Knee

Betreff: Zu viele Timer Events

BeitragMi, Dez 17, 2014 18:20
Antworten mit Zitat
Benutzer-Profile anzeigen
Hi, habt ihr auch schon mal das Problem gehabt, dass euch zwischen den Timer-Ticks die Zeit ausgegangen ist?

Ich erlebe es gerade, dass Timer wohl die ersten sind, die bei WaitSystem gefragt werden. Das resultiert darin, dass keine anderen Events mehr durch kommen.

Konkret habe ich einen Grafik-Timer bei 30Hz und einen Netzwerk Timer bei 100Hz. Letzteren musste ich auf 30Hz drosseln, weil ich sonst einfach keine Tastatur eingaben bekommen hätte. Ich binde die Aufgaben direkt mit AddHook EmitEventHook an die Timer.

Die Event-Queue hilft glaube ich nur wenig wenn man mit WaitEvent ohnehin dann auf den Timer wartet. Und wieder alles erledigt.

Einzige Lösung, die mir einfällt ist Multithreading. Ein Thread überwacht die Events und verteilt die Aufgaben an andere Threads bevor der nächste Timer tickt. Aber mit Multithreading kauft man sich ein Haufen anderer Probleme. Daher die Frage ob es irgendeine andere Lösung für das Problem gibt.

DAK

BeitragDo, Dez 18, 2014 23:47
Antworten mit Zitat
Benutzer-Profile anzeigen
Multithreading ist hierfür genau das Richtige. Es ist nicht so schwer, wie man es sich vorstellt und gerade bei solchen Sachen (asynchrones Netzwerk und Grafik) passt es genau. Das hilft hier sogar so gut, dass z.B. unter Android das OS verbietet, Netzwerksachen auf dem Grafikthread laufen zu lassen.

Und wenn man Multithreading beherrscht, dann tut man sich in Uni und Job wesentlich leichter Wink
Gewinner der 6. und der 68. BlitzCodeCompo

Silver_Knee

BeitragFr, Dez 19, 2014 2:04
Antworten mit Zitat
Benutzer-Profile anzeigen
Das "Problem" ist, dass man Timer nicht an Threads binden kann. Elegante Lösung wäre es je ein Timer für Dauerhaftes (Physik, Zähler), Grafik und Netzwerk zu haben. Input wird gänzlich über Events abgebildet. Der Netzwerk-Thread bekäme bei WaitSystem nur den Netzwerk-Poll Timer (noch schicker wäre ein Socket-ReadAvail-Event), der Graphik-Thread nur den Graphik-Timer (der am besten mit der Framerate gekoppelt ist) und ein dritter Thread (von mir aus der Main-Thread) bekäme die Inputs und den dauerhaften Timer.

Stattdessen bekommtt man bei jedem Aufruf von WaitSystem irgendein Event (oder?). Da muss man dann die Aufgaben in den verschiedenen Threads ansteuern.

Oder ist es eine gute Idee sich ganz von Timern zu verabschieden un in alle threads "Hauptschleifen" zu stecken und die irgendwie anders (z.B. While time<Millisecs() Delay 1 Wend) auf eine gleichmäßige Ausführung zu bringen?

Das Problem ist auch, dass ich überhaupt nicht einschätzen kann dank dinger wie diesem hier, ob und wann ich Sachen im Haupt-Thread machen muss.

Manche Sachen sind klar: Bei minib3ds Renderworld waäre es ungünstig in einem anderen Thread gleichzeitig die Positionen der einzelnen Objekte zu verschieben, also müssen diese Befehle halt auf die Ausführung von RenderWorld warten. Aber muss RenderWorld z.B im Main-Thread gemacht werden?

Die Unsicherheit macht mir mehr Sorgen als die Threads selbst.

DAK

BeitragFr, Dez 19, 2014 2:21
Antworten mit Zitat
Benutzer-Profile anzeigen
@Timer: Warum verwendest du WaitSystem und nicht WaitTimer? Damit kannst du für jeden Thread einen Timer erstellen und jeweils den Thread mittels WaitTimer so lange pausieren, bis der Timer tickt. Kein Bedarf hier für Events, mach es alles mit Threads, Timern und Schleifen.

@Threadsicherheit: Vergiss Threadsicherheit. Jeder gemeinsame Ressourcenzugriff sollte gesichert sein.

@Thread-Architektur:
Sinnvolle Aufteilungen wären z.B.:

Render
Update
Netzwerk

Das ist allerdings etwas schwieriger, da Update und Render ziemlich viele gemeinsame Ressourcen besitzen (beinahe alle), was den Vorteil der Threads wieder abschwächt.

Nimm Threads für den Anfang nicht unbedingt als eine Performance-Steigerung (was heißt, jeder Thread sollte gleich viel Last haben), sondern als eine Vereinfachung für dich (also, trenne Threads dort, wo es dir die Arbeit erleichtert).

Hier würde für die Aufteilung also folgendes genügen:

Update und Render
Netzwerk

Dazu mach die Kommunikation zwischen Netzwerk- und Hauptthread so, dass du jeweils eine Input- und eine Output-Liste hast (beide gesichert). Wenn der Netzwerkthread was empfängt, dann schreibt er die Werte in die Input-Liste (als Objekte). Der Update-Thread checkt jeden Frame ob was in der Liste hängt, und übernimmt die Änderungen. Dann schreibt er seine Updates in die Output-Liste, wo sie dann vom Netzwerkthread abgeholt und abgeschickt werden.

Noch was Prinzipielles: der Netzwerkthread sollte nicht 3x so schnell updaten wie der Update-Thread, sondern eher 1/3x, oder noch weniger. Auf jeden Fall aber nicht schneller als der Update-Thread.
Gewinner der 6. und der 68. BlitzCodeCompo

Silver_Knee

BeitragFr, Dez 19, 2014 8:55
Antworten mit Zitat
Benutzer-Profile anzeigen
Klingt alles sehr gut bis auf den ersten Part mit dem Timer. WaitTimer macht intern repeat waitsystem until timerticks-timerticksbefore>0 (oder so ähnlich). Wenn ich jetzt in mehreren threads waittimer gleichzeitig mache, kommen input-Abfragen (die auf EmitEventHook liegen) in einem zufälligen thread heraus. Müsste ich dann wieder die Event-Queue nehmen (deren PostEvent übrigens auch nicht Thread safe ist) und wenn die abfrage zwischen 2 waitsystem wieder so lange dauert weil ich erst update und render mache, kommt es dann nicht wieder zu den verlorenen input Events?

Wie wärs damit: Anstatt auf timer warten grafik und Netzwerk-thread auf eine condvar und der event thread, der der einzige mit waitsystem ist, signalisiert je nach geticktem timer die entsprechende condvar.

DAK

BeitragFr, Dez 19, 2014 11:15
Antworten mit Zitat
Benutzer-Profile anzeigen
Warum machst du den Input über Events?
Du kannst ohne Weiteres jeden Frame mit KeyHit() oder KeyDown() bzw. MouseHit() oder MouseDown() checken, ob die Taste gedrückt wurde. Dann braucht es dich nicht zu interessieren, was für Events es gibt. Wegen der Performance mach dir da keine Sorgen. Wenn du diesen Check jedes Grafikframe laufen lässt, dann kostet dich das fast keine Leistung. Und öfter als jedes Grafikframe brauchst du es eh nicht, da du genaueren Input ohnehin nicht anzeigen kannst.
Gewinner der 6. und der 68. BlitzCodeCompo

Silver_Knee

BeitragSa, Dez 20, 2014 17:35
Antworten mit Zitat
Benutzer-Profile anzeigen
Okay. Ich dachte Abarbeitung von Events wenn sie auftreten ist Polling vorzuziehen. Vor allem wegen dem zusätzlichen Overhead.

DAK

BeitragSo, Dez 21, 2014 12:51
Antworten mit Zitat
Benutzer-Profile anzeigen
Da gibts ein großes Jein.

Events haben Vorteile gegenüber Polling, allerdings ist die Implementierung in BMax recht schlecht und nur sinnvoll für Singlethread-Programme.

In den meisten Event-Implementierungen in anderen Sprachen kann man Funktionen oder Listener-Objekte registrieren, die bei Auftreten des Events aufgerufen werden. Das ist leicht über Threads skalierbar und hat den Vorteil, dass nicht überprüft werden muss, welches Event die Funktionen onTimerTick() oder onSpaceKeyDown() auslöst, da diese Funktionen einfach von Anfang an fix nur für die passende Art Event registriert werden.

Die BMax-Implementierung hingegen erlaubt, soweit ich verstehe, nur das Pausieren des aktuellen Threads bis irgendein Event geworfen wird. Danach müsste man erst überprüfen, was für ein Event das überhaupt ist, um dann passend reagieren zu können. Wenn du dann noch mehrere Timer und Tastendrücke über dieses System laufen lässt, dann machst du damit entgültig den Geschwindigkeitsvorteil des Eventhandlings im Vergleich zum Polling zunichte.
Und dadurch, dass hier viele verschiede Event-Arten vom gleichen Stück Code bearbeitet werden geht auch der sonstige Lesbarkeitsvorteil von Events flöten.

Wenn du Polling verwendest, dann kannst du dafür auch Multithreading verwenden, das heißt, du kannst die bislang völlig ungenutzten Ressourcen weiterer Prozessorkerne verwenden. Du verlierst also keine Leistung sondern gewinnst viel. Dadurch, dass du das Polling auch nicht häufiger machst, als du Frames renderst, kostet es dich auch auf dem pollenden Thread fast nix. bei 60 Polls/sec verbrauchst du auf einem aktuellen Prozessor weniger als 0.1% der Rechenleistung.

Du könntest dir auch ein sinnvolles Eventhandling auf der oben beschriebenen Basis bauen, wobei du einen eigenen Thread für das Polling verwendest.
Gewinner der 6. und der 68. BlitzCodeCompo

Silver_Knee

BeitragMo, Dez 22, 2014 20:42
Antworten mit Zitat
Benutzer-Profile anzeigen
Nunja also ich hab im Moment halt alles so über eine Kette von Hooks gelöst. Ich habe mehrere GUI-Elemente und schließlich das Spiel, die alle auf die Events reagieren.
Das umzustellen auf Pollen wäre jetzt schon etwas schwerig.

Ich habe allerdings jetzt auch festgestellt, dass Events nur vom MainThread richtig getriggert werden. Es geht quasi gar nix ohne den Main-Thread: OpenGL läuft (außer mit viel gewalt) nur auf einem Thread (Ausgabe) und die Events laufen nur auf dem Main-Thread ein (Eingabe).

Die Verarbeitung verteile ich gerade auf einen dritten Thread. Jetzt wird das aber mit der Synchronisation zum Problem.

-- EDIT --

So. Die Sache ist wohl wirklich, dass gerade bei Timer das Event.Fire nicht allzu lang dauern darf.

Ich habe jetzt wieder eine klassische Hauptschleife, einen Thread und eine (BlitzPlus-like)-EventQueue. Einfach allen Input auf EmitEventHook abwälzen ist wohl nicht.

BlitzMax: [AUSKLAPPEN]

Repeat
If TGame._instance And TGame._instance._selectedShip
TGame._instance.Physics()
EndIf

RMIPollNetwork

While PollEvent()
If CurrentEvent.id=EVENT_MOUSEDOWN And CurrentEvent.data=2 DebugStop;Exit
RunHooks INPUT_HOOK , CurrentEvent
If CurrentEvent.id = EVENT_APPTERMINATE End
Wend

HandleGraphics(0,Null, Null)
WaitTimer timer
Forever


Wie man sieht kann man aber die Hooks weiter verwenden.

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG Allgemein

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group