Thread jede Millisekunde etwas machen lassen

OP #7393425
Lesenswert?

In meiner Anwendung soll jede Millisekunde etwas parallel gemacht werden. Ich befürchte, ich habe den falschen Ansatz, da ich aus der µC Welt komme.

Als GUI nutze ich wxWidgets. Ich habe zwei Ansätze, in beiden Fällen werden die 1 Millisekunde natürlich nicht eingehalten, es sind eher ein paar 100 Millisekunden Abstand zwischen den Aufrufen. In der

1
loop()

werden wirklich nur ein paar Additionen durchgeführt, keine große Berechnung.

  1. Einen std::thread laufen lassen:
1
th = std::thread([this]() {
2
  while (1) {
3
    this->loop(); 
4
    wxMilliSleep(1);
5
  }
6
}
  1. Einen wxTimerEvent nutzen:
1
timer = new wxTimer(this, ID_TIMER);
2
timer->Start(1);
3

4
Bind(wxEVT_TIMER, &MainWindow::loop, this, ID_TIMER);
5

6
void MainWindow::loop(wxTimerEvent& event) {
7
  //mache etwas
8
  timer->Start(1);
9
}
#7393509
Lesenswert?

Sofern das ganze unter Windows läuft, kann die Timergranularität mit den sogenannten "Multimediafunktionen" verfeinert werden. Normalerweise liegt die Granularität bei 10 oder sogar 15 msec, mit der Win32-API-Funktion timeBeginPeriod kannst Du das in ganzen Millisekunden auf 1 msec reduzieren.

https://learn.microsoft.com/en-us/windows/win32/api/timeapi/nf-timeapi-timebeginperiod

#7393530
Lesenswert?

Harald K. schrieb:

Sofern das ganze unter Windows läuft, kann die Timergranularität mit den sogenannten "Multimediafunktionen" verfeinert werden. Normalerweise liegt die Granularität bei 10 oder sogar 15 msec, mit der Win32-API-Funktion timeBeginPeriod kannst Du das in ganzen Millisekunden auf 1 msec reduzieren.

Muss wohl echt ein Windows Ding sein. Selbst mit Python und wxPython ist die eine Millisekunde bei Linux überhaupt kein Problem (und die CPU Last nicht wahrnehmbar).

Und das obwohl (Standard)Python und Threading keine besonders effizienten Freunde sind.

(Firma: egal) #7393658
Lesenswert?

Keine A. schrieb:

da ich aus der µC Welt komme.

Willkommen in der Welt der nicht Echtzeit tauglichen Betriebssysteme:-)

Vereinfacht: Wenn du einen Thread für eine bestimmte Zeit Schlafen schickst, bedeutet das für das OS nur das die angegebenen Zeit die frühest mögliche Zeit ist wo dein Thread überhaupt mal wieder in der Warteschlange Platznehmen darf. Und wenn das OS dann irgendwann mal Lust dazu hat bekommt dein Thread auch mal wieder etwas Rechenzeit zugeteilt. Je nach Priorität und anderen Aufgaben kann das durchaus mal einige Millisekunden (zusätzlich) dauern bis du mal wieder dran bist.

Das ganze ist also kein Timer der eine genau definierte Zeit wartet und danach unverzüglich weiter arbeitet. Windows, Linux und Unix geben sich da übrigens praktisch nichts, die arbeiten da alle sehr ähnlich.

#7393667
Lesenswert?

Harald K. schrieb:

Das hat nichts mit Effizienz, sondern der Frequenz des Timerinterrupts zu tun. Und die liegt halt auf Windowssystemen üblicherweise bei 100 Hz. Früher™ war das noch schlimmer, da lag sie bei nur 18.2 Hz.

Naja, das hat sehr wohl auch was mit der Effizienz zu tun. Es gibt (für Windows genauso wie für Linux) Echtzeit-Patches, die das Task-Switching in die Größenordnung 1ms bringen. Allerdings: bei beiden OS ist das erwartungsgemäß mit insgesamt einer deutlichen Verschlechterung der Leistung des Gesamtsystems verbunden.

Genau deshalb wendet man diese Patches auch nicht für normale Installationen an.

#7393696
Lesenswert?

C-hater schrieb:

Norbert schrieb:

Muss wohl echt ein Windows Ding sein. Selbst mit Python und wxPython ist die eine Millisekunde bei Linux überhaupt kein Problem

LOL

Du hast ja mal definitiv überhaupt keine Ahnung. Was glaubst du, wozu es die Echtzeit-Patches für den Kernel gibt, wenn Linux das schon von Hause aus angeblich so problemlos handeln kann?

Glaubst du Clown allen Ernstes das eine MILLISEKUNDE ein ernstzunehmendes Problem darstellt? Du meine Güte, ich habe so etwas ständig hier laufen. Manchmal sind es vielleicht 1020µs, manchmal auch 1050µs. Aber das ist es dann auch. Möglicherweise magst du dich auch einmal mit NUMA/Prioritäten/expliziten Kernzuweisungen usw. beschäftigen.

Du kannst aber auch gerne weiter rumquatschen.

Beitrag #7393717 wurde von einem Moderator gelöscht.
#7393756
Lesenswert?

MaWin O. schrieb:

C-Hater hat vollkommen recht. Ohne PREEMPT_RT ist das Einhalten der Millisekunde pure Glückssache.

Es geht nicht um das präzise Einhalten, soviel sollte doch klar sein. Es ging hier die ganze Zeit um einen 1ms Timerevent. Und wenn man mit einer seltenen Abweichung im - sagen wir 100µs Bereich - leben kann.

Schnell hingeklopfter Beispielcode (und ein Log):

1
#!/usr/bin/python3
2
# -*- coding: utf-8 -*-
3
import array
4
import time
5
import signal
6

7
def cb_signal(signum, stackframe):
8
    global idx, end_all
9
    arr[idx] = time.time()
10
    idx += 1
11
    if idx >= MAX_COLLECT:
12
        signal.setitimer(signal.ITIMER_REAL, 0,0)
13
        end_all = True
14

15
MAX_COLLECT = 1000+1
16
end_all = False
17
arr = array.array('d', (0 for _ in range(MAX_COLLECT)))
18
idx = 0
19
signal.signal(signal.SIGALRM,cb_signal )
20
signal.setitimer(signal.ITIMER_REAL, 0.001, 0.001)
21
while not end_all:
22
    time.sleep(.1)
23

24
for i in range(1, MAX_COLLECT):
25
    delta_us = (arr[i] - arr[i-1]) * 1E6
26
    print(f'{1000-delta_us:+8.3f} µs')

Aufruf: python3 PC_test_timer.py und dann: nice -10 python3 PC_test_timer.py Und wenn das noch nicht reichen mag, kann man auch noch exklusiv einen Kern reservieren. Dann wird's noch schöner.

Erzählt mir bitte nicht das Dinge nicht gehen, wenn ich sie hier ständig laufen habe. Nun kann's jeder ausprobieren. Danke.

Angehängte Dateien:
#7393762
Lesenswert?

MaWin O. schrieb:

Norbert schrieb:

Glaubst du Clown allen Ernstes das eine MILLISEKUNDE ein ernstzunehmendes Problem darstellt?

C-Hater hat vollkommen recht. Ohne PREEMPT_RT ist das Einhalten der Millisekunde pure Glückssache.

Erfahrungsgemäß funktioniert es ohne PREEMPT_RT mit einer Millisekunde schon recht gut, wenn nicht gerade noch andere rechenlastige Prozesse oder eine Auslagerungsorgie läuft. Aber man muss mit ein paar Ausreißern rechnen. Mit PREEMPT_RT ist die Millisekunde tatsächlich kein Problem. Und der Prozess muss natürlich für sich realtime-Scheduling aktivieren und sich gegen Auslagerung sperren - und vorher die Berechtigung dazu bekommen haben. Weiterhin wartet man nicht für eine bestimmte Dauer, sondern bis zu einem bestimmten Zeitpunkt, den man dann immer in Millisekundenschritten hochzählt. Das habe ich seit fast 15 Jahren in mehreren Systemen erfolgreich so im Einsatz.

#7393769
Lesenswert?

Rolf M. schrieb:

Erfahrungsgemäß funktioniert es ohne PREEMPT_RT mit einer Millisekunde schon recht gut, wenn nicht gerade noch andere rechenlastige Prozesse oder eine Auslagerungsorgie läuft. Aber man muss mit vereinzelten Ausreißern rechnen.

Auch wenn man explizit einen Kern nur für diese Anwendung sperrt? Und die Priorität erhöht (den nice Wert absenkt)? Dafür sind ja nur wenige Zeilen in einer Konfigurationsdatei einzutragen und man darf das als normaler User.

Ausreißer? So etwas ist mir noch nie unter gekommen - was aber nicht heißt das es nicht möglich wäre. Allein der sich dahinter versteckende Mechanismus würde mich natürlich interessieren.

Weiterhin wartet man nicht für eine bestimmte Dauer, sondern bis zu einem bestimmten Zeitpunkt, den man dann immer in Millisekundenschritten hochzählt.

Genau das macht ›titimer‹, darum hat man manchmal wenige Mikrosekunden weniger, dann wieder wenige Mikrosekunden mehr.

#7393772
Lesenswert?

Norbert schrieb:

Rolf M. schrieb:

Erfahrungsgemäß funktioniert es ohne PREEMPT_RT mit einer Millisekunde schon recht gut, wenn nicht gerade noch andere rechenlastige Prozesse oder eine Auslagerungsorgie läuft. Aber man muss mit vereinzelten Ausreißern rechnen.

Auch wenn man explizit einen Kern nur für diese Anwendung sperrt? Und die Priorität erhöht (den nice Wert absenkt)? Dafür sind ja nur wenige Zeilen in einer Konfigurationsdatei einzutragen und man darf das als normaler User.

Man darf als normaler User einen Kern komplett für eine spezifische Anwendung reservieren? Aber so habe ich das noch nicht getestet.

Ausreißer? So etwas ist mir noch nie unter gekommen - was aber nicht heißt das es nicht möglich wäre. Allein der sich dahinter versteckende Mechanismus würde mich natürlich interessieren.

Woher sie genau kommen, weiß ich nicht, aber ich konnte sie durchaus messen. Das war aber wie gesagt ohne PREEMPT_RT. Mit dem Preempt-Kernel waren diese Ausreißer nicht zu sehen. Das hat mir dann auch deutlich gezeigt, dass Preempt durchaus wichtig ist.

Weiterhin wartet man nicht für eine bestimmte Dauer, sondern bis zu einem bestimmten Zeitpunkt, den man dann immer in Millisekundenschritten hochzählt.

Genau das macht ›titimer‹, darum hat man manchmal wenige Mikrosekunden weniger, dann wieder wenige Mikrosekunden mehr.

Ich nutze clock_nanosleep(), das auch zu den Realtime-Funktionen zählt. Dem kann man sagen, welche "clock" es nehmen soll, und man kann wählen, ob man relativ oder absolut warten will.

Beitrag #7393773 wurde von einem Moderator gelöscht.
#7393779
Lesenswert?

Rolf M. schrieb:

Man darf als normaler User einen Kern komplett für eine spezifische Anwendung reservieren?

Grins. Na ja, root muss es ihm schon irgendwann mal erlauben… Genau wie die Prioritätserhöhung. Core spezifizieren geht ohne Erlaubnis, da es sich ja um ein selbst auferlegtes weiteres Limit handelt.

Woher sie genau kommen, weiß ich nicht, aber ich konnte sie durchaus messen. Das war aber wie gesagt ohne PREEMPT_RT. Mit dem Preempt-Kernel waren diese Ausreißer nicht zu sehen. Das hat mir dann auch deutlich gezeigt, dass Preempt durchaus wichtig ist.

Muss ich mir bei Gelegenheit mal bauen, vielleicht macht's einige Dinge noch einfacher. Wie gesagt, kleine Unschärfen im sub100µs Bereich beobachte ich auch. Stören aber keine meiner Routinen.

Ich nutze clock_nanosleep(), das auch zu den Realtime-Funktionen zählt. Dem kann man sagen, welche "clock" es nehmen soll, und man kann wählen, ob man relativ oder absolut warten will.

Ja, so wie bei setitimer: ITIMER_REAL,ITIMER_VIRTUAL,ITIMER_PROF Nur da muss man nicht selber warten und zählen, es gibt einen konstanten Strom von Signalen die callbacks auslösen.

#7393795
Lesenswert?

Norbert schrieb:

Rolf M. schrieb:

Man darf als normaler User einen Kern komplett für eine spezifische Anwendung reservieren?

Grins. Na ja, root muss es ihm schon irgendwann mal erlauben… Genau wie die Prioritätserhöhung.

Ok, das passt schon eher zu dem, wie ich es mir vorstelle.

Muss ich mir bei Gelegenheit mal bauen, vielleicht macht's einige Dinge noch einfacher.

Das ist das schöne bei Debian. Da gibt's den immer fertig als Paket. Einfach apt install linux-image-rt, booten und fertig.

Ja, so wie bei setitimer: ITIMER_REAL,ITIMER_VIRTUAL,ITIMER_PROF Nur da muss man nicht selber warten und zählen, es gibt einen konstanten Strom von Signalen die callbacks auslösen.

Bei clock_nanosleep gibt's noch CLOCK_MONOTONIC, für das es bei setitimer soweit ich weiß keine direkte Entsprechung gibt. Und ich muss dann eben nicht den Umweg über einen Signalhandler gehen, sondern arbeite direkt in einer while-Schleife, die am Ende jedes Durchlaufs einfach auf den nächsten Tick wartet. Und warten musst du ja auch irgendwie.

MaWin O. schrieb:

Norbert schrieb:

sagen wir 100µs Bereich

Dieser Bereich ist PREEMPT_RT vorbehalten.

So große Abweichungen habe ich bei PREEMPT_RT aber auch noch nicht gesehen.

#7393817
Lesenswert?

MaWin O. schrieb:

Norbert schrieb:

sagen wir 100µs Bereich

Dieser Bereich ist PREEMPT_RT vorbehalten.

Norbert schrieb:

Erzählt mir bitte nicht das Dinge nicht gehen, wenn ich sie hier ständig laufen habe. Nun kann's jeder ausprobieren. Danke.

Du bist ein Schwätzer. Dass es bei dir funktioniert, beweist rein gar nichts.

Ich lese hier seit vielen Jahren mit und habe seit der Registrierungsumstellung noch nichts geschrieben. Aber für das oben stehende musste ich mich anmelden, weil das so nicht unkommentiert stehen bleiben darf.

Ich halte eigentlich sehr viel von MaWin Original, aber so pauschal ist seine Aussage leider ziemlich falsch.

Ich habe beruflich mit komplexeren Echtzeitregelungssystemen zu tun. Wir setzen dafür AMD EPYC Server mit viel RAM, cores (große Matrixmultiplikationen, AMD EPYCs wegen Speicherbandbreite Sensoren -> Server(MVM) -> Aktoren) ein, sowie 10/25/100 GbE Ethernet, um mit den Steuerelementen zu kommunizieren.

PREEMPT_RT braucht man nur, wenn man mehrere Prozesse auf dem selben Kern laufen lassen muss (z.B. weil CPU klein gewählt wegen Energiebedarf oder Bauteilkosten). Wenn man genügen Kerne hat und sie fest für je einen Thread reservieren kann, dann ist Echtzeit auch ohne PREEMPT_RT kein Problem.

Wir senden hier Ethernetpakete mit DPDK raus und haben auch den Messaufbau zum relativ genauen timestampen (100 GbE Sniffer mit PTP und PPS). Wir bekommen 0.2 µs Standardabweichung und max. 0.5 µs Absolutfehler vom Sollzeitpunkt. Über Tage. Fast saturierte 10GbE, Jubmo packets. Vanilla Kernel 5.15.106. Nimmt man normale UDP sockets statt DPDK sind es immer noch <10µs vom user Prozess bis zum Kabel. Mit XDP sockets + BPF könnte man vermutlich auch auf DPDK verzichten, bei gleich niedriger Latenz.

Man muss Linux entsprechend tunen, dass das geht. Kernel cmdline opts:

  • isolcpus=<cores>: auf die Kerne setzen, die isoliert werden sollen. Das weist den scheduler an, keine User-Tasks auf den Kernen zu schedulen, aber kernel threads und IRQs usw können schon noch vorkommen.
  • irqaffinity=<cores>: auf die anderen Kerne setzen (Komplement) als die, die isoliert werden sollen. Dadurch werden die ganzen IRQs by default nicht mehr auf den isolierten Kernen gescheduled. Das sollte man über die kernel cmdline machen anstatt im userspace, weil es manche IRQs gibt, die sich später nicht mehr verschieben lassen (AFAIK habe ich das mal mit nem RAID controller gesehen)
  • tsc=reliable: Bei modernen CPUs ist die TSC stabil. Linux schaut dennoch alle paar ms mal nach und prüft, ob der TSC gut ist. Mit "reliable" kann man das abschalten.
  • Dann haben wir noch nohz_full=<cores>, rcu_nocbs=<cores>, iommu=pt, aber die sind IIRC gar nicht notwendig.
  • cstates kann man auf entsprechende timeouts setzen. Manuell aus der Applikation oder mit tuned. Aber die bewegen sich IIRC so in 20µs Bereich.

Die Anwendung im userspace setzt beim Start die CPU affinity von den threads auf die isolierten Kerne, ein Startup-script schiebt die Netzwerk IRQs auf isolierte benachbarte cores davon. Man kann das ganze dann noch weiter spinnen mit hugepages + mlock.

(Wahrscheinlich kann man viele von den Sachen auch über cgroupsv2 setzen und nicht in der kernel cmdline, haben wir aber noch nicht ausprobiert.)

Prozessprioritäten muss man dann gar nicht nutzen. Wieso auch? Es läuft ja eh nur ein (gepinnter) Thread auf dem Kern.

Zum überprüfen kann (sollte) man das kernel tracing verwenden. Die Dateien kann man mit Kernelshark analysieren.

#7393824
Lesenswert?

Josef schrieb:

Wir setzen dafür AMD EPYC Server mit

Also wieder ein Beispiel. Und was sagt es aus? Dass es in deinem Fall funktioniert. Nicht mehr und nicht weniger.

PREEMPT_RT braucht man nur, wenn man mehrere Prozesse auf dem selben Kern laufen lassen muss (z.B. weil CPU klein gewählt wegen Energiebedarf oder Bauteilkosten). Wenn man genügen Kerne hat und sie fest für je einen Thread reservieren kann, dann ist Echtzeit auch ohne PREEMPT_RT kein Problem.

Das ist ganz großer Quatsch mit Soße. Auch ein Exclusiv-CPU-Task kann aufgehalten werden, wenn er durch Kernelressourcen blockiert wird, die in !PREEMPT_RT blockierend sind.

Wir senden hier

Schön.

#7393826
Lesenswert?

Was macht PREEMPT_RT? Es liefert einen unterbrechbaren Kernel. D.h. z.B. dass busy loops im Kernel durch andere Locking-Mechanismen ersetzt werden. Es liefert Prozessprioritäten, die sogar höher als Kernel threads sein können. Es liefert threaded IRQs, sodass ein IRQ später behandelt werden kann, wenn gerade ein hochpriorisierter task gescheduled werden muss.

Wenn man keine tasks hat, die um die selbe CPU "kämpfen", braucht man PREEMPT_RT nicht. Wenn man die RT priorities nicht nutzt, braucht man es eigentlich auch nicht, weil es sich wie ein normales Linux verhält.

PREEMPT_RT ist eine wirklich schöne Sache, wenn man auf limitierten Systemen arbeiten muss (z.B. nur 1 core, embedded). Aber wenn man für die Anwendung sowieso genügend cores hat (z.B. dual socket à 64 cores), dann bringt PREEMPT_RT gar nichts bzw. ist sogar langsamer/hat eine höhere Latenz, weil threaded IRQs und nicht-spinlocks verwendet werden.

Wie immer gilt: nicht blind glauben, sondern messen! Wir haben früher PREEMPT_RT verwendet ("wegen der Echtzeit", "normaler Linux kann das doch gar nicht") und haben aber diesen Mythos durch Messung und Tuning (und Kontakt mit Linux maintainern) aufklären können.

Wieso sollte es auch anders sein? Wenn man alle Störquellen (andere tasks, IRQs, TSC watchdog, ...) beseitigt hat, wieso sollte Linux einen Task von seiner CPU reißen? Um den idle task du schedulen? Ne...

#7393832
Lesenswert?

Josef schrieb:

Es liefert threaded IRQs,

Die sind immer vorhanden. RT erzwingt sie für alle Interrupts.

Wenn man keine tasks hat, die um die selbe CPU "kämpfen", braucht man PREEMPT_RT nicht.

Ja. Wenn das in deinem Fall so ist, dann ist das in deinem Fall so.

In meiner Kernelversion nimmt mein Task ein Kernel-Spinlock. Plötzlich ist die CPU blockiert. Was nun?

Aber wenn man für die Anwendung sowieso genügend cores hat (z.B. dual socket à 64 cores), dann bringt PREEMPT_RT gar nichts

Das ist halt Quatsch. Kannst du jetzt gerne noch 10 mal wiederholen. Bleibt Quatsch.

Wie immer gilt: nicht blind glauben, sondern messen!

Genau. Aber auch nicht von einem System auf die Welt schließen.

und haben aber diesen Mythos durch Messung und Tuning (und Kontakt mit Linux maintainern) aufklären können.

Schön für euren Fall.

wieso sollte Linux einen Task von seiner CPU reißen?

Es gibt noch mehr, als den Scheduler.

#7393838
Lesenswert?

MaWin O. schrieb:

In meiner Kernelversion nimmt mein Task ein Kernel-Spinlock. Plötzlich ist die CPU blockiert. Was nun?

Wieso sollte mein Task in kernel space gehen?

In DPDK mache ich keine syscalls und in XDP wären es schnelle read/write für die flags. Natürlich kann man nicht universell sagen, dass es nie vorkommen kann, aber für konkrete Systeme kann man das sehr wohl: Im heißen Pfad gibt es wenige und immer die gleichen syscalls (read, write). clock_gettime geht über die VDSO, kein syscall. Speicherallozierung macht man im heißen Pfad nicht. Also wo soll das spinlock herkommen? Man kann auch einfach das tracing mitlaufen lassen, solange man sich nicht sicher ist.

#7393841
Lesenswert?

MaWin O. schrieb:

Ohne PREEMPT_RT ist das Einhalten der Millisekunde pure Glückssache.

Wenn du einen Nieschenkerneltreiber verwendest, der nicht auf PREEMPT_RT angepasst wurde, ist das Einhalten der Millisekunde auch unter PREEMPT_RT pure Glückssache.

Und egal ob PREEMPT_RT oder nicht, die SMIs bleiben (noch nicht im echten Leben gemessen). In unserem Setup sind wir jetzt aber soweit, dass wir die PCIe4 Latenzen sehen können, z.B. mehrere NICs auf der selben Karte aber verschiedene cores/threads, die darauf schreiben. Das ist für uns ein tolerierbares Hintergrundrauschen.

#7393855
Lesenswert?

Josef schrieb:

Man muss Linux entsprechend tunen, dass das geht. Kernel cmdline opts:

  • isolcpus=<cores>: auf die Kerne setzen, die isoliert werden sollen. Das weist den scheduler an, keine User-Tasks auf den Kernen zu schedulen, aber kernel threads und IRQs usw können schon noch vorkommen.

Vielen Dank für Deine Ausführungen aus der Praxis, diese Methoden verwende ich auch schon seit einigen Jahren erfolgreich. Habt Ihr auch schon einmal eine Kombination mit exklusiven "cpusets" experimentiert?

#7393880
Lesenswert?

MaWin O. schrieb:

Also wieder ein Beispiel. Und was sagt es aus? Dass es in deinem Fall funktioniert. Nicht mehr und nicht weniger.

Ich warte ja immer noch auf eine Gegenbeispiel von dir. Solange das fehlt, sind deine Aussagen nichts weiter als heiße Luft. Bisher steht es: 3 Praxis-Beispiele, wo es funktioniert zu ein paar Rants, die behaupten, dass es eigentlich nicht funktioniert.

#7393917
Lesenswert?

Keine A. schrieb:

In meiner Anwendung soll jede Millisekunde etwas parallel gemacht werden.

Warum?

Welches Ereignis ist so wichtig, dass bei einer PC-Applikation der eigentliche Ablauf des Programms tausend mal pro Sekunde unterbrochen werden muss?

Ich befürchte, ich habe den falschen Ansatz, da ich aus der µC Welt komme.

Wenn Du uns sagen würdest, was Du eigentlich machen willst, könnten wir Dir da weiterhelfen...

#7395030
Lesenswert?

Purzel H. schrieb:

Sonst wird das eh nichts. Ein PC ist ein Visualisierungsgeraet, kein Controller

Mit PREEMPT_RT ist das eben nicht so.

Sicher ist man auch mit PREEMPT_RT nicht ganz unabhängig von der Hardware. Wenn z.B. ein SMI oder NMI ständig reinhaut, dann kann das OS da gar nichts machen. Aber generell sind mit PREEMPT_RT, geeigneter HW und z.B. dem Deadline-Scheduler Deadlines von 100 µs problemlos und zuverlässig möglich. Eine 1 ms Deadline is geradezu ewig lang für PREEMPT_RT und erzeugt einem solchen Kernel keinerlei Schweißperlen auf der Stirn.

Wenn es mit der Deadline-Anforderung deutlich unter 100 µs geht, dann sollte man tatsächlich so langsam über einen eigenen Controller nachdenken.

Moderator (Firma: Titel) Persönliche Seite #7396461
Lesenswert?

Harald K. schrieb:

mit der Win32-API-Funktion timeBeginPeriod kannst Du das in ganzen Millisekunden auf 1 msec reduzieren.

Jittert aber wie blöd.

Josef schrieb:

Nieschenkerneltreiber

"Nieschen" wie der Name "Lieschen"? Oder wie die bekannte "Nische"?

Beckhoff hat auch so eine Treiberschicht für sein TwinCAT. Wenn das läuft (und das tut es automatisch, weil der beim Booten geladen wird), dann hast du auch mal eine Chance, nach langer Zeit wieder einen Bluescreen zu sehen.

Und den anderen Kollegen hat es deswegen laufend aus den Teams-Besprechungen rausgehauen. Jetzt hat er die Beckhoff-SW wieder vom Office-Rechner deinstalliert und einen eigenen PC dafür aufgesetzt.

#7396883
Lesenswert?

Josef schrieb:

Und egal ob PREEMPT_RT oder nicht, die SMIs bleiben (noch nicht im echten Leben gemessen).

Das soll wohl sehr vom Board abhängig sein. Teilweise werden darüber in dem BIOSen irgendwelche Work-Arounds für Hardware-Bugs integriert. Deshalb macht OSADL Messungen mit aller möglicher Hardware, um solche Problemkinder zu finden.

Lothar M. schrieb:

Josef schrieb:

Nieschenkerneltreiber

"Nieschen" wie der Name "Lieschen"? Oder wie die bekannte "Nische"?

Vielleicht wenn jemand niest: "Ich habe ein Nieschen gemacht". 😉

#7398180
Lesenswert?

Sheeva P. schrieb:

Vielen Dank für Deine Ausführungen aus der Praxis, diese Methoden verwende ich auch schon seit einigen Jahren erfolgreich. Habt Ihr auch schon einmal eine Kombination mit exklusiven "cpusets" experimentiert?

Wie weiter oben schon geschrieben, haben wir das noch nicht ausprobiert aber wie haben es vor. Eigentlich sollte es funktionieren, aber testen muss man es trotzdem. Der Vorteil wäre, dass man nicht ab Boot die Cores für normale Nutzung im System verliert (Software auf einem 128 Kerner bauen ist schon schön), sondern dynamisch mit den cpusets einschalten kann, wann man sie isoliert/exklusiv braucht.

#7400911
Lesenswert?

Habe was ähnliches gestern mit C# ausprobiert, einmal mit einem endlos loopenden backgroundworker + sleep, https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker?view=net-7.0 und einmal mit einem timer, der regelmäßig feuert, https://learn.microsoft.com/en-us/dotnet/api/system.threading.timer?view=net-7.0.

Beides war recht ernüchternd.

Der Timer hat einen overhead von ~8-12 ms pro Event - man kann einen Offset, der vermutlich nur für mein System gilt abziehen, damit wird die Genauigkeit bei periodischen Triggern etwas höher - aber kleiner als 15 ms ohne dass irgendwas in dem callback gemacht wird, kam ich nicht.

Ein endlos (lang) laufender BGW ist eigentlich nicht empfohlen (wird aber häufig auf SO mit einem cancellation token gezeigt), insbesondere, wenn dort ein Sleep vorkommt, um timings zu simulieren - funzt aber besser als die Timer Lösung. Auch da gibt es einige ms overhead, die man offenbar abziehen kann. Minimale sample time (Sleep(1)) war dann so bei 5-8 ms.

Hätte auch gedacht da geht mehr.

Der PC (Windows 10, dicke CPU) hat sich in allen Varianten gelangweilt, instrumentation hat gezeigt, dass die meiste Zeit im Kernel drauf geht - ich denke schneller ist das Kontextwechseln einfach nicht.

Edit: https://stackoverflow.com/a/73535286/3620376 kommt auf ähnliche Zahlen (5-8 ms), offenbar gibt es 15 ms Zeitscheiben, interessant.

(Firma: Privat) #7401007
Lesenswert?

Naja, Windows ist halt immer noch kein echtes Multitasking- System. Dank der neuen Prozessoren und mit mehr Kernen ist es seit dem Anfang schon ziemlich nahe dran gekommen, aber halt immer noch nicht ganz (Zeitscheiben).

Deshalb denke ich, daß da immer ein paar ms bleiben, egal wie man es nun anstellt.

#7472234
Lesenswert?

Hallo!

Wer unter Windows "mehr" braucht als Sleep() bietet, der kann auch die ntdll.dll anzapfen. Dieser kleine Thread unten hält beispielsweise die 256-Bit-Stages auf Trab, indem aller 0,7 ms (das ist die -7000 unten) ein entsprechender Befehl ausgeführt wird. Das funktioniert bis -5000, also 2000 mal pro Sekunde. Die Zeiteinheit ist also 100ns, das Minus bedeutet "relative Zeiten".

Das ist kein Busy-Loop, die CPU-Last geht nur etwa 1-2% hoch. Viel mehr sollte man dort aber wirklich nicht erledigen wollen. Wer den "counter" alle paar Sekunden ausliest wird sehen, daß das funzt.

1
int iThread_off = 0;
2
long long counter = 0;
3

4
DWORD WINAPI KeepYMMAlive(LPVOID param) {
5
  static NTSTATUS(__stdcall * NtDelayExecution)(
6
    BOOL Alertable, PLARGE_INTEGER DelayInterval) = 
7
    (NTSTATUS(__stdcall*)(BOOL, PLARGE_INTEGER)) 
8
    GetProcAddress(GetModuleHandle("ntdll.dll"), "NtDelayExecution");
9

10
  static NTSTATUS(__stdcall * NtSetTimerResolution)(IN ULONG RequestedResolution, 
11
    IN BOOLEAN Set, OUT PULONG ActualResolution) = 
12
    (NTSTATUS(__stdcall*)(ULONG, BOOLEAN, PULONG)) 
13
    GetProcAddress(GetModuleHandle("ntdll.dll"), "NtSetTimerResolution");
14

15

16
  ULONG actualResolution;
17
  NtSetTimerResolution(1, true, &actualResolution);
18
  LARGE_INTEGER interval;
19

20

21
  interval.QuadPart = -7000;
22
  while (!iThread_off) {
23
    _asm { // inline-asm geht mit clang
24
      vxorps ymm0, ymm0, ymm0
25
    }
26
    counter++;
27
    NtDelayExecution(false, &interval);
28
  }
29

30
  NtSetTimerResolution(actualResolution, true, &actualResolution);
31

32
  return 1;
33
}
#7478839
Lesenswert?

Heinz B. schrieb:

Deshalb denke ich, daß da immer ein paar ms bleiben, egal wie man es nun anstellt.

Ich habe in Tcl/Tk eine Zählroutine gestartet, die eine Variable um eins hochzählt und sich dann nach 1 ms (mit "after 1 zaehlroutine") erneut aus der Eventqueue startet. Erstaunlicherweise (und völlig unerwartet) habe ich alle Sekunde die Zählvariable um 1000 hochgehen sehen. Könnte sein, daß Windows inzwischen besser ist, als gedacht. Habe aber gerade keine Hardwarekarte zur Hand, um das Ganze auch auf dem Oszi zu verifizieren.

Gruß Klaus (der soundsovielte)

#7478973
Lesenswert?

Hier ein Oszillogramm einer Ausgabe von 0xC0 auf UART mit 115,2 KBaud jede Millisekunde in Windows 10 auf einem 10 Jahre alten Fujitsu-Rechner (Haswell). scheint gut zu funktionieren, selbst in einer Skriptsprache wie Tcl. Der von Heiko (zwergnr8) beschriebene Weg wird wohl vermutlich die Grundlage dafür abgeben.

Gruß Klaus (der soundsovielte)

Angehängte Dateien:
#7479284
Lesenswert?

Also meiner Erfahrung nach kannst du unter Windows nicht wirklich unter 10ms kommen. Was aber eventuell möglich wäre, über NOPs den aktuellen Thread blockieren (habe ich selber noch nie gemacht).

Und solange Standard-Windows kein Echtzeit-OS wurde (was komplett an mir vorbei gegangen wäre) kannst du dich nie und nimmer auf "Echtzeit" verlassen.

900ss D. schrieb:

Dann beschäftige den Rechner mal während der Messung. Ich fürchte dann sieht das nicht mehr so gut aus. Z.B. Firefox starten.

Wäre jetzt auch mein Ansatz gewesen, oder irgend ein Virenscanner, OneDrive-Aktualisierung im Hintergrund laufen lassen...

Gast #7479528
Lesenswert?

Klaus S. schrieb:

aus der Eventqueue startet. Erstaunlicherweise (und völlig unerwartet) habe ich alle Sekunde die Zählvariable um 1000 hochgehen sehen. Könnte sein, daß Windows inzwischen besser ist, als gedacht. Habe aber gerade keine Hardwarekarte zur Hand, um das Ganze auch auf dem Oszi zu verifizieren.

Unter Linux hat man immer ~80ms zusätzlich in Summe, egal wie oft man after aufruft.

1
#!/usr/bin/tclsh
2

3
set max 1000
4

5
set i 0
6
set t1 [clock milliseconds]
7
#puts "time1 is: $t1"
8

9
while  { $i < $max } {
10
   incr i
11
   #puts $i
12
   after 1
13
}
14
set t2 [clock milliseconds]
15
#puts "time2 is: $t2"
16

17
puts "diff: [expr $t2 - $t1]"
#7480168
Lesenswert?

Habe noch drei weitere Versuche gemacht. Wenn ich (wie Herbert) in Tcl den Befehl "after 1" verwende, bekomme ich (wie von Harald erklärt) die Prozeßgranularität von Windows zu sehen, alle 12 bis 15 Millisekunden gibts eine Ausgabe (war unter XP auch nicht anders).

Unter Debian12 (Bookworm) bekomme ich mit "after 1" alle 2,5 Millisekunden eine Ausgabe und mit der sich selbst replizierenden Prozedur "einsaus" mit der Anweisung "after 1 einsaus" darin etwa alle 1,2 Millisekunden eine Ausgabe.

Das sehe ich auf dem Oszi und das sind für mich die Fakten. Plattitüden wie die Tatsache, daß das Timing unter Last in die Knie geht wissen nur blutige Anfänger nicht. Es geht meines Erachtens in diesem Thread nicht darum, wie man eine 1-ms-Routine verhindert, sondern wie man sie erreichen kann. Verhindern geht am Besten ohnehin durch Ausschalten, das spart dann auch noch Energie.

Gruß Klaus (der soundsovielte)

P.S. Für alle Nicht-Tcler: Der Befehl mit dem schönen Namen "after" macht je nach Aufrufart 2 verschiedene Dinge. Mit einem einzigen übergebenen Parameter legt er den gesamten Prozeß schlafen, mit 2 übergebenen Parametern sortiert er den als Parmeter2 übergebenen Namen eines Unterprogramms mit der als Parameter1 übergebenen Wartezeit in die Eventqueue ein.

PS2: Danke Heiko und Harald, ich habe was dazugelernt.

#7480203
Lesenswert?

Klaus S. schrieb:

Plattitüden wie die Tatsache, daß das Timing unter Last in die Knie geht wissen nur blutige Anfänger nicht.

Ach das hatte ich ganz vergessen, die lesen hier ja nicht mit. ;)

Und es wäre durchaus interessant gewesen, wie stark der Einfluss ist. Ansonsten ist es immer noch reine Theorie die gezeigt wird. Es könnte theoretisch so sein aber in der Praxis hat ein Rechner nun mal auch mit Rechenlast zu tun.

#7480284
Lesenswert?

900ss D. schrieb:

Und es wäre durchaus interessant gewesen, wie stark der Einfluss ist. Ansonsten ist es immer noch reine Theorie die gezeigt wird. Es könnte theoretisch so sein aber in der Praxis hat ein Rechner nun mal auch mit Rechenlast zu tun.

Ich habe gezeigt, daß es geht, praktisch, nicht theoretisch. Es hat mich selbst erstaunt, ich hätte vor diesen Experimenten gesagt: über 100 Hz läuft nix, da braucht man ein Echtzeitsystem. Ich habe eine frei verfügbare Scriptsprache verwendet, deren Grundzüge in 10 Minuten erlernbar sind, so daß jeder meine Messungen wiederholen kann. Wenn also jemand daran interessiert ist, weitere Informationen zu erhalten, so steht ihm nichts im Weg. Ich weiß, wie ein Rechner unter Last reagiert. Wenn Du es nicht weißt, dann bekomme einfach das Hinterteil in die Höhe und mache diese Experimente. Zeig einfach, daß Du mehr kannst, als was Du bisher gezeigt hast.

Möchtest Du die 10 Minuten Tcl-Lernerei vermeiden, so kann ich auch mein Programm komplett vöffentlichen, bisher bin ich davon ausgegangenen, daß die hier Anwesenden das auch ohne Nachhilfe hinbekommen. Ich irre mich aber öfters mal.

Gruß Klaus (der soundsovielte)

#7480344
Lesenswert?

…und hier mal kurz einen Quickie vom Laptop herunter-gerotzt. KEIN RT-LINUX! Standard debian, lahmarschige CPU, einen Kern isoliert und das (c++) Progrämmchen mit nice -10 für 10 Sekunden (10000 events) laufen lassen.

Timer feuert jede Millisekunde (1000µs). Die 10k Timings in array geschrieben und zum Ende ausgegeben und in GnuPlot geschoben. Timer beim Start nicht auf Millisekunden-Beginn synchronisiert (egal um Regelmäßigkeit zu messen), feuert zumeist bei xxx715µs, hat aber oftmals bis zu 4µs Verspätung und gelegentliche Ausreißer um 9µs, einmal sogar um 20µs.

Aber bitte ignoriert diese Ergebnisse, es wurde ja bereits zuvor festgelegt das so etwas nicht möglich ist. ;-)

Und ja, das ist selbstverständlich kein RT Verhalten, aber um einmal jede Millisekunde etwas zu machen…

Angehängte Dateien:
#7480533
Lesenswert?

Klaus S. schrieb:

daß die hier Anwesenden das auch ohne Nachhilfe hinbekommen

Äh? Was hat dich denn so schlecht gelaunt gemacht? Meine Güte....

Klaus S. schrieb:

Ich habe gezeigt, daß es geht, praktisch, nicht theoretisch.

Unter Last hat du es nicht gezeigt. Und genau das(!) wäre praktisch interessant gewesen und nicht dieses Aufgeplustere dass du in der Lage bist, in 10 Minuten ein Tcl-Programm zu schreiben was eine Zeitmessung macht. Und so wie die gemacht wurde ist die völlig irrelevant weil praxisfern. Ja ich weiß schon, du hast es praktisch gemacht... Respekt, ich bin wirklich beeindruckt. Du bist in der Lage eine Zeitmessung durchzuführen :)

Gast #7480602
Lesenswert?

900ss D. schrieb:

Unter Last hat du es nicht gezeigt. Und genau das(!) wäre praktisch interessant gewesen und nicht dieses Aufgeplustere dass du in der Lage bist, in 10 Minuten ein Tcl-Programm zu schreiben was eine Zeitmessung macht.

Läuft zuverlässig, solange du dem Task eine hohe Priorität gibst, am besten Realtime, sonst funken irgendwelche superwichtigen Windows Hintergrunddienste dazwischen. Benötigter Speicher muss vorher alloziert werden, und Dateizugriffe müssen ganz am Anfang oder am Ende durchführt werden - dann läuft das stabil. Den Defender ausmachen. Linux funktioniert ähnlich gut, da habe ich keine Unterscheide gesehen. Der Jitter der Uart Kommunikation liegt typisch unter 0.5 Millisekunden. Zumindest einfache Sachen wie editieren und kompilieren geht nebenbei ganz gut. Unter Windows verwendet man CreateWaitableTimer und WaitForSingleEvent. Wenn man unter 15 ms kommen möchte, dann muss man die Timer Resolution mit NtSetTimerResolution reduzieren, was die Grundlast durch die Interrupts etwas erhöht.

#7480741
Lesenswert?

Klaus S. schrieb:

daß Du mehr kannst als Sprüche zu klopfen

Der einzige der unfreundlich Sprüche geklopft hat warst du. Mein Gedanke war, wenn du eh den Test schon am laufen hast, wäre Aufwand nicht mehr sehr hoch, das nochmal laufen zu lassen und dabei auch Last zu erzeugen. Aber es gibt Leute die schaffen das nicht, können nur unfreundlich werden und das Thema "Last" als Platitüde abtun.

Deshalb schrieb ich dieses: 900ss D. schrieb im Beitrag #7479023:

Dann beschäftige den Rechner mal während der Messung.

Moderator Persönliche Seite #7480757
Lesenswert?

Meine Erfahrung mit diversen RT-Linuxen hat mich gelehrt, dass es selbst auf Echtzeitsystemen nicht so einfach ist, bspw. 1ms auch unter Last reproduzierbar sauber hinzubekommen.

Das hängt von einigen Faktoren - gerade auch der Hardware - ab. Nicht umsonst gibt es bspw. bei LinuxCNC Listen von Boards, die gut, die weniger gut und die gar nicht geeignet sind.

Unter LinxcCNC laufen üblicherweise zwei RT-Threads: ein sehr schnell getakteter mit 40-50kHz und einer mit eben dieser 1ms.

Selbst wenn man den schnellen Thread rauswirft (weil die Schritterzeugung für einen Schrittmotor nicht notwendig ist bzw. von externen FPGAs erledigt wird), hat man immer noch ordentlich Jitter (hier aktuell 40-50µs) drin. Damit kann ich in meinem Fall aber leben.

Deutlich reduzieren kann man den Jitter unter Linux (wie schon oben geschrieben) durch Isolierung von einzelnen Kernen, die der Kernel bei der Verteilung der Prozesse dann einfach ignoriert. Auf diesen lässt man dann (nur) seine Echtzeitanwendung laufen.

Stichworte dazu sind "isolcpu" als Kernelparameter und "taskset" für die manuelle Zuteilung von Prozessen auf bestimmte Kerne. Damit komme ich hier auf unter 10µs Jitter für den 1ms-Thread.

Zu den Tcl-Programmen: Ich würde vermutlich mit "clock microseconds" arbeiten. Selbst bei einem Jitter hätte man dann doch relativ feste 1ms-Zeitscheiben, die nicht "wandern". Falls das wichtig ist.

#7481068
Lesenswert?

Norbert schrieb:

…und hier mal kurz einen Quickie vom Laptop herunter-gerotzt. KEIN RT-LINUX! Standard debian, lahmarschige CPU, einen Kern isoliert und das (c++) Progrämmchen mit nice -10 für 10 Sekunden (10000 events) laufen lassen.

Du weisst aber schon, dass (wie auch Chris angemerkt hat) eine isolierte CPU und entsprechend hohe Priorität einem RT-Gepatchten Linux schon verdammt nahe kommt. Wenn du dann explizit nur 1 Thread auf dieser CPU laufen lässt hat du die exklusiv zur Verfügung. Je nach Setup kommen dann maximal noch ein paar Interrupts auf der CPU, respektive wenn keine Last da ist nicht einmal das.

Ausserdem teile ich die Erfahrungen (meine beziehen sich explizit auf TSN und Echtzeit-Ethernet im Steuerungsbereich) von Chris auch, dass es selbst bei einem RT-Linux verdammt schwierig ist, den Jitter langzeitstabil tief zu halten. Weil wenn du nach 1 Woche Laufzeit einen einzigen Ausreisser von 10ms von deinem 1ms Task hast, hast du halt schon nicht erfüllt. Selbst wenn alle anderen bei 10us sind.

#7481092
Lesenswert?

Patrick B. schrieb:

Du weisst aber schon, dass (wie auch Chris angemerkt hat) eine isolierte CPU und entsprechend hohe Priorität einem RT-Gepatchten Linux schon verdammt nahe kommt.

Nein, das kommt dem überhaupt nur nahe, wenn man einen reinen Compute-Thread hat, der keinerlei Syscalls macht. Sobald Syscalls gemacht werden, ist es hin mit der RT-Fähigkeit, wenn kein RT-Kernel verwendet wird.

#7481109
Lesenswert?

Patrick B. schrieb:

Du weisst aber schon, dass (wie auch Chris angemerkt hat) eine isolierte CPU und entsprechend hohe Priorität einem RT-Gepatchten Linux schon verdammt nahe kommt.

Ja klar, ich mach' den Senf schon Jahrzehnte. Es ging mir darum zu zeigen, das ›normale‹ Aufgaben sich sehr wohl recht nah an den vorgegebenen Timeslices ausrichten lassen. Zudem wurde vom TE auch niemals RT-Fähigkeit und extremeste Präzision gefordert.

Dies wurde erst später - und völlig unnötigerweise - in den Thread hinein gequetscht um Unruhe und Verwirrung zu stiften.

Und dann wurde verzweifelt nach Möglichkeiten mit blockierenden Syscalls gesucht, nur um doch noch irgendwie ein wenig Recht zu behalten. Aber da war der Thread schon längst entgleist und verloren.

Leider nicht wirklich überraschend hier auf µC-Net.

#7481116
Lesenswert?

Norbert schrieb:

Dies wurde erst später - und völlig unnötigerweise - in den Thread hinein gequetscht um Unruhe und Verwirrung zu stiften.

Aber da war der Thread schon längst entgleist und verloren.

Leider nicht wirklich überraschend hier auf µC-Net.

Eine Diskussion, die nicht nach deiner Meinung verläuft, ist entgleist, verloren und Unruhe. Werd mal erwachsen.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren