Hallo,
Ich wusste zwar auch nicht immer was mit einer Delta-Queue anzufangen, aber so wie ich dein Wissen einschätze, habe ich eigentlich gedacht das du das Prinzip kennst.
Also bei deiner Methode, musst du doch auch den Thread in eine sortierte Queue reinpacken. Also musst du auch dort linear, ein Element nach dem Anderen durchgehen.
Ich denke schon das ich das Prinzip der Delta-Queue verstanden hab, aber ich bin der Meinung das die für diesen Zweck eher ungeeignet ist.
Wenn Du auf dem vordersten Element rumrechnest dann kommt auch immer ein Speicherschreibzugriff zusätzlich, gegenüber einem Vergleich, dazu. Zum prüfen, im IRQ-Handler, ob das vorderste Element nun dran ist müssen wir beide auch nur das vorderste Element anfassen (Du lesen und schreiben, ich nur lesen), das komplette durchgehen der Liste ist dafür nicht erforderlich (siehe meinen IRQ-Handler-Code). Zum beliebigen Einfügen eines neuen Elements musst Du Deine Delta-Queue in jedem Fall linear von vorne an durchlaufen bis die richtige Stelle gefunden ist, ich dagegen kann diese Liste als Baum realisieren und kann daher sehr viel schneller Einfügen. Das Entfernen des vordersten Elementes, also das Element unten links in meinem Baum, ist nur minimal komplizierter als bei Deiner Delta-Queue.
Ich weiß wovon ich schreibe, ich habe diese Baum-Variante schon mal implementiert (vor knapp 9 Jahren), in purem x86-32Bit-Assembler, mit absoluten 64Bit-Zeitwerten. Das war für ne Industrie-Maschine mit vielen Pneumatik-Zylindern, Motoren und haufenweise anderem Zeugs. Für jede Bewegung wurde ein TimeOut-Timer gestartet so das dort oft über 100 Timer (mit Laufzeiten von etwa 200ms bis etwa 3s, aber auch ein paar Timern im Stundenbereich) gleichzeitig aktiv waren. Da die meisten der Timer wieder gelöscht wurden bevor sie abgelaufen sind, da die Bewegungen ja normalerweise rechtzeitig fertig waren (anders wäre es ein Fehler gewesen), und dann wieder neue Timer gestartet wurden war das einfügen von Timern ein essenziell wichtiger Vorgang. Die gelöschten Timer wurden nicht aus dem Baum entfernt, das erschien mir zu kompliziert, (entfernt habe ich solche Timer nur wenn sie nur ein Kind haben) sondern einfach deaktiviert und wenn sie dann abgelaufen sind (dann haben die garantiert maximal nur ein Kind) einfach ohne weitere Aktion gelöscht. Zu dem Timing-System gibt es noch einen Message-Handler und einen Event-Handler welche beide auf dem Timing-System aufsetzen (für TimeOuts usw.). Auch ein kooperativer Thread-Scheduler war mit dabei, der wiederum vom Timing-System benutzt wurde. Mit diesem System bin ich auf einem K6-500MHz auf knapp 600'000 Thread-Wechsel pro Sekunde gekommen, die ungefähr 20 Threads haben auch was sinnvolles getan.
Ich geb dir mal folgenden Pseudo-Code:.......
Ich hoffe dieser Code (ist erstmal fehlerfrei ) und du verstehst jetzt mein Prinzip.
Ähm, nein, leider überhaupt gar nicht.
Deinen Code hab ich ehrlich gesagt gar nicht verstanden, Bitte poste ihn noch mal mit reichlich und aussagekräftigen Kommentaren dabei. Was mich stört ist das Du da das einzufügende Element ständig modifizierst und auch das Queue-Element vor (wirklich davor? oder habe ich mich da verschaut? ) welches das neue kommt wird auch modifiziert. In meiner Variante wird nur die richtige Stelle gesucht (nur Vergleiche keine Modifikationen) und dann eingefügt (simple Pointer-Magie, so wie bei Dir), egal ob lineare sortierte Liste oder sortierter Baum, schon allein deshalb (keine zusätzlichen/verteilten Schreibzugriffe) sollte meine Variante schneller sein.
Ich muss immer nur vom Head einer Queue einen Wert abziehen,
Du meinst im IRQ-Handler?
ist also genauso effizient wie dein Vergleich. Denn ob ich einen Vergleich mache (was intern auch nur ne Subtraktion ist) oder ob ich etwas abziehen und bei den Flags dann auf 0 prüfe (was unter C leider nicht möglich ist, glaub ich jedenfalls) ist im Endeffekt das selbe!
Beim richtigen rechnen kommt eben noch der Speicherschreibzugriff dazu. Da Du einzelne Werte (viel kleiner als eine Cache-Line), quer über den RAM verteilt, modifizierst könnte das schon recht gut bremsen. Auf den Cache kannst Du dabei jedenfalls nicht zählen, der dürfte IMHO eher bremsen weil er versucht komplette Cache-Lines zu lesen und die modifizierten Cache-Lines dann wieder komplett schreiben will (oder sie im Cache liegen bleiben und dort wertvollen Platz kosten).
Im Endeffekt sind unsere Systeme sich schon ähnlich
Ja, nur das ich mit absoluten Werten arbeiten will und diese IMHO weniger (Rechen-)Arbeit machen.
nur das ich den PIT so langsam wie möglich laufen lassen möchte (bzw. ihn sogar ganz deaktivieren, wenn er nicht gebraucht wird).
Wenn Du den PIT abschalten möchtest musst Du das in GetTicks() berücksichtigen oder soll dann in Deinem OS die Zeit stehen bleiben? Wenn es Dir aufs Energiesparen ankommt würde ich eher dazu raten die PIT-Frequenz zu reduzieren, alle 100ms die CPU für ein paar Mikrosekunden aufzuwecken dürfte nur wenig Energie kosten. Wenn Du deutlich besseres Power-Management willst (was eh erst auf recht modernen CPUs und Boards geht) dann solltest Du auch den HPET voraussetzen (der braucht zum laufen fast gar nichts an Energie).
aber eigentlich müsste das OS so eine Funktion bereitstellen und dafür war ja auch mal der TSC gedacht, bevor Intel erst hinterher festgestellt hat, das es dumm ist wenn sich die Aktualisierungsrate mit der Frequenz ändert
Das der TSC von der CPU-Core-Frequenz abhängig ist finde ich gar nicht so schlecht, das macht ihn mit den anderen Performance-Monitoring-Counters (z.B. Anzahl der abgearbeiteten Befehle, nur Integer-Befehle, nur FPU-Befehle, Cache-Hits usw.) vergleichbar. Der TSC ist nur zum Messen von kleinen Zeiträumen gedacht, für präzise Langzeitmessungen gibt es ja den HPET oder die RTC.
Was ich noch nicht ganz verstehe ist, das du sachen bemängelst die bei deinem System doch genauso sind.
Ich habe bemängelt das bei der Delta-Queue mehr zu rechnen ist und das Du nicht effizient neue Elemente einfügen kannst. Das sind beides Dinge die mit absoluten Zeit-Werten nicht auftreten und das Einfügen lässt sich noch zusätzlich durch die Benutzung eines Baumes deutlich beschleunigen.
Du hättest nur den Vorteil, das du mit absoluten Werten ne andere Struktur als ne Liste (z.B. nen RB-Tree) nehmen könntest, aber auch du musst sortiert eintragen (was in einer Liste nun mal linear ist)
Absolute Zeitwerte haben den Vorteil das man diese immer beliebig miteinander Vergleichen kann (also jeden mit jeden) wogegen bei relativen Zeit-Werten immer der Bezugspunkt mit eingerechnet werden muss. Wegen der beliebigen Vergleichbarkeit der absoluten Zeitwerte muss man bei der linearen sortierten Liste auch nicht zwingenst jedes Element anfassen, da könnte man eventuell in großen Sprüngen über die Liste gehen bis man in der Nähe des gewünschten Ziels ist und dann immer feiner suchen (dazu bräuchte man natürlich einen effizienten wahlfreien Zugriff auf die Liste). Zumindest stehen mit den absoluten Zeitwerten ein menge Möglichkeiten offen, es gibt bestimmt noch was besseres als nen einfacher Baum, wogegen bei den relativen Zeitwerten der erforderliche Bezugspunkt einiges ziemlich kompliziert macht.
oder du müsstest bei jedem IRQ Aufruf die komplette Liste durch gehen, was alles andere als optimal ist.
Zumindest da sind wir uns offensichtlich einig.
Was das Beeinflussen des HPET-Counters betrifft. Wenn ich das alles richtig Verstanden habe, dann gibt man nun nen Comperator (einen Wert, in dem Fall nen absoluten) an und der HPET feuert dann nen IRQ wenn der Counter diesen Wert erreicht hat.
Exakt. Der HPET feuert nur bei Gleichheit, weshalb man mit Zeitwerten die gleich dran sind etwas aufpassen muss (aber das sollte der HPET-IRQ-Handler aus meinem letzten Post theoretisch hinbekommen).
Ich würde das wieder über ne Delta-Queue lösen, einfach auf den alten Wert der schon drin steht den neuen Wert vom 1. Element der Delta-Queue drauf addieren und fertig. Ganz simpel
Ganz simpel finde ich das nicht. Zum draufaddieren musst Du vorher lesen und das wird in einem Non-Cachable-Bereich eher langsam sein. Wenn Du zum Schluss eh absolute Werte brauchst, warum dann so viel Mühe um immer mit relativen Werten zu arbeiten?
Ich will damit sagen das Du mit dem häufigen Auslesen des PIT-Zählers trotzdem keine genaueren IRQs bekommst, Du also trotzdem auf dem 55ms-Raster hängen bleibst.
Dann hast du leider mein Konzept immernoch nicht verstanden
Ganz offensichtlich.
Ich lese den Counter aus um zu wissen wieviel Zeit seit dem letzten PIT IRQ schon wieder vergangen ist, damit ich diese auf die Zeit, die der Thread schlafen will drauf rechnen kann. Denn der IRQ Handler vom PIT zieht immer 55,4ms ab, egal wann der Thread schlafen gelegt wurde. So sichere ich das der Thread mind. genauso lange schläft wie er das vor hat.
Um zu sichern, das ein Thread auch genauer als das 55ms Raster schlafen kann, habe ich ja eine per CPU Sleep-Queue die für alles <55ms zuständig ist und da ich da wieder mit ner Delta-Queue arbeite, ist auch mein Jitter nicht so schlimm. Denn der kann sich ja nicht immer wieder aufsummieren (jedenfalls nicht was die Sleep-Queue betrifft).
Das sieht in meinen Augen irgendwie extrem Aufwendig aus. Ich verstehe nicht welchen Vorteil Du Dir von diesem Aufwand erhoffst. Das ganze kann man doch eigentlich ganz einfach und simpel Lösen.
Wie gesagt, da der Scheduler-Timer nur für alles <55ms zuständig ist,
Also ich finde es ungeschickt wenn der Scheduler was mit Timing zu tun hat. Ich persönlich bin der Meinung das der Schuler
nur für die Verwaltung der Threads (und deren Zustände wie lauffähig, blockiert, schlafend usw.) und für den Kontext-Switch zuständig ist. Ein ordentlicher Scheduler, der mehrere CPUs fair + schnell über die Threads verteilt und dafür effiziente + leistungsfähige Management-Funktionen bietet, ist ansich schon kompliziert genug, da muss man nicht auch noch
fremde Aufgaben mit rein packen.
Gerade da ein Thread mit normaler Prio bei mir 30ms als Zeitscheibe bekommt, wird der Scheduler im average-case 2mal aufgerufen
Auf einem normalen System dürfte kaum eine Zeitscheibe aufgebraucht werden, üblicherweise läuft ein Thread ziemlich schnell auf eine blockierende Anfrage (aus Datei lesen, ins Netzwerk senden oder auf User-Eingabe warten). Nur Threads die wirklich was zu rechnen haben werden ihre Zeitscheiben aufbrauchen. Die Länge der Zeitscheiben zu optimieren bringt IMHO nicht viel, da ist ein effizienter Kontext-Switch schon nützlicher.
und der Jitter ist dort selbst mit PIT "gerade" mal (3*832ns für lesen beim reinpacken des Threads + 5*832ns pro Scheduleraufruf= 13*832ns=) 10816ns= 10,8µs, was ich für verdammt gut für so ne alte Krücke halte Wenn du jetzt noch den APIC-Timer für den Scheduler nimmst, dann wird es noch genauer!
Von was für einen Jitter schreibst Du da eigentlich? Die exakte Länge der Zeitscheiben ist eher unkritisch, 10% hin oder her dürften da gar nichts machen. Viel wichtiger ist das wenn ein Thread mit ner hohen Priorität zum Zeitpunkt X wieder aufgeweckt werden soll dass das auch möglichst zum rechten Zeitpunkt passiert und dieser Thread dann auch einen anderen Thread mit niedrigerer Priorität verdrängen kann.
Das einzige Problem was ich haben werde, sind halt PCs die nur nen PIT haben, da summiert sich dann der Jitter auf,
Nur wenn Du den PIT nicht frei laufen lässt. In der PIT-Variante meines Ursprungsposts wird auch
nichts aufakkumuliert.
obwohl ich gerade ganz spontan mit dem Gedanken spiele in dem Fall die RTC dafür zu mißbrauchen Also das ich die RTC für's Grobe nutze und den PIT für's "feine", dann summiert sich mein Jitter auch nicht auf. Muss ich mal sehen ob ich das irgendwie vernünftig über mein Interface umsetzen kann.
Mehrere unabhängige Timer (die RTC ist ja auch ein Timer) zu benutzen halte ich für keine gute Idee. Du wirst ne Menge Aufwand treiben müssen um das auch bei sehr langer Laufzeit (über Monate oder gar Jahre, oder baust Du ein OS mit konstruktiv begrenzter Laufzeit? ) synchron zu halten.
Genau deshalb stelle ich mir die Frage: Warum Kompliziert wenn es doch auch so einfach geht? Was erhoffst Du Dir davon? Wo genau siehst Du den Vorteil?
Relative Zeitwerte, mehrere unabhängige Timer und verwische Zuständigkeiten in den SW-Modulen. Klar das kann man alles realisieren aber wenn Du diesen Aufwand treiben möchtest dann erwartest Du doch auch was, ich sehe das nur eben nicht, das müsstest Du mir erklären.
Ich persönlich bin der Meinung das eine zusätzliche Ebene (schlafen legen und aufwecken musst Du den Thread ja trotzdem) auch zusätzliche Zeit kostet.
Ja, aber nur wenn das Event noch nicht ausgelöst wurde. Was willst du denn dann machen oder besser gesagt, wie willst du dann warten.
Wenn der Event noch nicht ausgelöst wurde (was sich ja einfach feststellen lässt) dann wird die aktuelle Thread-ID im Event-Object als wartend vermerkt und der Thread einfach schlafen gelegt. Wenn das Event dann irgendwann ausgelöst wird dann muss der Thread wieder geweckt werden und wenn kein Thread wartet dann eben nicht. Nur auf einem SMP-System muss man da etwas mit der Atomizität dieser Aktionen aufpassen.
(ich hätte vielleicht sagen sollen, das er so lange wartet bis er aufgeweckt wird und nicht eine bestimmte Zeit schläft)
Ja, davon bin ich ausgegangen. Wobei man als zusätzliches Feature ja noch ein optionales TimeOut einbauen kann. Meine damalige Implementierung kann das.
Da muss ich dich dann mal ganz frech nach deinem Alter fragen und wie lange du dich schon mit der OS Programmierung auf x86 beschäftigst
Das findest Du in meinem
Forums-Profil und meiner
Wiki-Benutzer-Seite. Wieso fragst Du das, weil ich wissen möchte wo Du so exotische Boards her hast?
Denn den APIC haben die alle drin, der wird aber immer deaktiviert,
Das wusste ich nicht das die APICs vom BIOS per Default deaktiviert werden.
Meine Erfahrung mit solchen Boards habe ich mit 4x PentiumPro 200MHz 1MB-L2-Cache unter Windows NT gemacht und ich kann Dir sagen das Windows NT verdammt schlecht skaliert hat. Das Board und die CPUs hatten zwar alle APICs aber Windows NT konnte damit nicht allzu viel anfangen. Der IO-APIC war wimre komplett deaktiviert und die Local-APICs wurden von Windows NT nur mit dem absolut notwendigsten (für SMP) benutzt.
Wenn du willst mache ich dir nen Foto von dem einen Sockel5 Board was ich hier habe?
Ja, gerne. Was haste denn für Prozies drauf? Falls Du das Board nicht in Betrieb bekommst würde ich mich dafür interessieren.
Um es mal so zu sagen, OS Programmierung ist doch ne Extreme Sportart unter den Programmierern
Full ACK!
Aber man kann den Triathlon bei normalem Wetter (leicht Bewölkt, kein Regen und kein Wind) in normaler Umgebung (Flachland) durchziehen oder, wenn man masochistisch veranlagt ist, bei extremsten Wetter (>100 l Regen pro m² und h mit Windstärke 8 ) und in unpassenster Umgebung (Gebirge).
SCNR
Grüße
Erik