Pipes in Linux und CPU-Affinitäten

Antworten
Buschel
Aktiver Hörer
Beiträge: 989
Registriert: 12.12.2013, 20:12
Wohnort: Raum Karlsruhe

Pipes in Linux und CPU-Affinitäten

Beitrag von Buschel »

Hallo zusammen,

seit einiger Zeit setze ich einen RT Kernel ein und die Audiowiedergabe läuft über kodi und eine nachfolgende pipe in der Art: kodi(dec) > sox(i2f) > resample_soxr(src) > brutefir(drc) > sox(f2i) > sox/playhrt(write):

- kodi(dec): Audioformat nach PCM dekodieren
- sox(i2f): int nach float64
- resample_soxr(src): SampleRateConversion
- brutefir(drc): Raumkorrektur
- sox(f2i): float64 nach int (incl. Dithering)
- sox/playhrt(write): Schreiben auf das alsa output device

Code: Alles auswählen

pcm.pipe_src96k_drc_dither_playhrt {
	type file
	file "	  | chrt -f 80 taskset -c 2 sox -D -v 0.8 -r 44.1k -b 32 -c 2 -e signed -t raw - -b 64 -c 2 -e float -t raw - \
		  | chrt -f 81 taskset -c 2 resample_soxr -c 2 -B 89 -P 50 -e 28 -i 44100 -o 96000 \
		  | chrt -f 82 taskset -c 2 brutefir -quiet -nodefault /home/buschel/Convolver/DRC_96k_stdio.conf \
		  | chrt -f 83 taskset -c 2 sox -t raw -r 96k -b 64 -c 2 -e float - -b 32 -c 2 -e signed -t raw - dither -p 24 \
		  | chrt -f 90 taskset -c 3 playhrt --stdin -k 2 -s 96000 -f S32_LE -d usbaudio --mmap --verbose --sleep=1000000"
	format "raw"
}
Pipe 1: resample_soxr/brutefir/sox auf demselben Core

Leider habe ich immer wieder (sehr) sporadisch Aussetzer oder Klicks gehört. Diese wurden vermutlich durch buffer underruns verursacht, die ich leider nur zum Teil angezeigt bekam. Ich habe mir deswegen in den letzten Tagen noch einmal die jeweilige Priorität und CPU-Affinität der einzelnen Prozesse angeschaut, und auch mittels cyclictest die Latenzen von Testprozessen gemessen. Diese Testprozesse haben eine geringere oder gleiche Priorität wie die signalverarbeitenden Prozesse der Audiopipe -- also sox, resample_soxr, brutefir und playhrt. Auf Basis dieser Messungen kann man sehen wie lange ein Prozess auf einem CPU-Core wartet bis er an die Reihe kommt. Interessanterweise sehe ich, dass brutefir und resample_soxr in meiner Konfiguration bis zu mehreren tausend µs den jeweiligen CPU-Core blockieren. Dabei blockiert resample_soxr bis maximal etwa 5.000 µs und brutefir bis maximal etwa 4.000 µs (Bild 1). Wenn beide auf auf dem gleichen Core laufen, werden andere Prozesse bis zu >15.000 µs blockiert. Sprich: Die formatwandelnden sox-Prozesse, die die Daten am Eingang und Ausgang der pipe verarbeiten, können bis zu mehr als 15 ms verzögert werden. :shock:

Bild
Bild 1: Latenzen 0-8.000 µs (Core 0: RT tasks, Core 1: resample_soxr, Core 2: brutefir/sox, Core 3: playhrt)

In meiner bisherigen Audiopipeline liefen brutefir, resample_soxr sowie die formatwandelnden sox-Prozesse alle auf demselben Core, und nur kodi und der in das alsa-device schreibende Prozess liefen jeweils auf einem eigenen Core (Pipe 1). Ich gehe davon aus, dass im worst-case die pipeline durchaus in buffer underruns ging, und habe jetzt die CPU-Affinität so angepasst, dass brutefir und resample_soxr auf unterschiedlichen CPU-Cores laufen (Pipe 2). Damit werden die formatwandelnden sox-Prozesse um maximal 4.000 µs aufgehalten. Das wiederum sollte über die generell eingesetzten Pufferlängen abgefangen werden. Ich werde mir das jetzt eine Weile anhören und abwarten, ob ich immer noch die sporadischen Aussetzer und Klicks erlebe.

Code: Alles auswählen

pcm.pipe_src96k_drc_dither_playhrt {
	type file
	file "	  | chrt -f 80 taskset -c 2 sox -D -v 0.8 -r 44.1k -b 32 -c 2 -e signed -t raw - -b 64 -c 2 -e float -t raw - \
		  | chrt -f 81 taskset -c 1 resample_soxr -c 2 -B 89 -P 50 -e 28 -i 44100 -o 96000 \
		  | chrt -f 82 taskset -c 2 brutefir -quiet -nodefault /home/buschel/Convolver/DRC_96k_stdio.conf \
		  | chrt -f 83 taskset -c 2 sox -t raw -r 96k -b 64 -c 2 -e float - -b 32 -c 2 -e signed -t raw - dither -p 24 \
		  | chrt -f 90 taskset -c 3 playhrt --stdin -k 2 -s 96000 -f S32_LE -d usbaudio --mmap --verbose --sleep=1000000"
	format "raw"
}
Pipe 2: resample_soxr auf anderem Core als brutefir/sox

Auch interessant ist der Unterschied von sox und playhrt beim Schreiben auf das alsa-device. Während sox andere Prozesse auf demselben den Core bis zu 500 µs warten lässt (Bild 2), reduziert resample_soxr das auf maximal 40-50 µs (Bild 3).

Bild
Bild 2: Latenzen 0-8.000 µs(Core 0: RT tasks, Core 1: resample_soxr, Core 2: brutefir/sox, Core 3: soxplay)

Bild
Bild 3: Latenzen 0-400 µs (Core 0: RT tasks, Core 1: resample_soxr, Core 2: brutefir/sox, Core 3: playhrt)

Tja, jetzt merke ich, dass meine CPU zu wenig Cores hat. Gerne würde ich eigene Cores für kodi (1), sox-Formatwandlung (2), SRC (3), DRC (4), alsa-write (5) und noch einige Cores für das OS spendieren. Das ruft nach einer 8-Core CPU. :mrgreen:

Viele Grüße,
Andree
Bild
bastelixx
Aktiver Hörer
Beiträge: 306
Registriert: 08.11.2015, 17:31

Beitrag von bastelixx »

Hallo Andree,

eine abgefahrene Methode Musik zu hören. Kannst du mit sicherheit sagen, dass die manuelle Festlegung für Prozessen-Priorität die Vorteile gegenüber der automatisierte Prozesssteuerung durch Sytem bringt? Hast du es ausprobiert? Sind die Latenzen nicht überbewertet. Ich habe bei Holger (schoko-sylt) sein System auf Windows Systembasis gehört, da waren die Latenzen vom Gesamtsystem einfach gigantisch, trotzdem die Musikalität auf dem höchsen Niveau, was ich jeweils gehört habe.
Ich konnte leider nicht vergleichen, ob Musikwiedergabe mit Playhr von Frank musikalische Vorteile gegenüber zum MPD bringt, mir war es auch zu viel Handarbeit um die Musiktitel zu wiedergeben. Ein gewisses Komfort neben der Musikalität steht bei mir auch ganz oben auf der Liste.

Gruß
Stanislaw
Bild
frankl
Aktiver Hörer
Beiträge: 489
Registriert: 20.01.2013, 01:43
Wohnort: Aachen

Beitrag von frankl »

Hallo Andree,

ich fand es beim Herumprobieren mit CPU-Affinitäten am besten, wenn die Prozesse mit den kritischten (Pipe- oder Shared Memory-)Übergängen auf dem gleichen Core liefen.

In Deiner Pipe würde ich mal probieren, das `sox` vor dem `playhrt` auf dem gleichen Core laufen zu lassen, denn das ist der kritischte Übergang.

Ich liste hier mal auf, wie ich die Prozesse bei mir aufteile. Der Realtime Kernel wird mit dem Parameter `isolcpus=1,2,3` bei vier Cores gestartet.
Ich entkoppel meine Pipe mit Hilfe von `writeloop`. Der erste Teil macht das Rechnen:

Code: Alles auswählen

resample_soxr (liest flac aus shared memory, gibt 64 bit floats aus)  |  brutefir  (gibt 32 bit integer aus, ohne dithering)  |  writeloop (schreibt in kleine shared memory Puffer),
Der zweite Teil wird etwa 0.5 Sekunden später gestartet, es ist nur ein

Code: Alles auswählen

bufhrt (liest die von writeloop gefüllten Puffer und sendet die Daten über Ethernet zum Minirechner, der das eigentliche Abspielen macht)
Mit `top` sieht man die Verteilung so (Spalte `P` ist die Affinität), die CPU-Zeiten schwanken nur so um +/-0.2%:

Code: Alles auswählen

  PID USER      PR     VIRT    RES    SHR S  %CPU  %MEM     TIME+ P COMMAND  
 4168 xxxxxxx   20    61532  58332  11060 R  73,1   1,4   0:52.93 2 /cache/brutefir /cache/fromcache.conf -quiet   
 4173 root     -61     2200    456    384 S  12,6   0,0   0:09.11 1 /cache/bufhrt --buffer-size=8192 --input-size=768 --bytes-per-second=1536000 --loops-per-second=1000 --overwrite=0 --port=5570 --extra-bytes-per-second=-3 --shared +  
 4162 xxxxxxx   20   221640   4280   2564 S  12,1   0,1   0:08.85 3 /cache/resample_soxr --buffer-length=3072 --shmname=/data.flac --outrate=192000 --band-width=85.0 --phase=13 --fading-length=150000 --param-file=/cache/VOLRACE192   
  542 root     -51        0      0      0 S   7,8   0,0  41:19.68 1 [irq/42-eth0]   
  543 root     -51        0      0      0 S   7,3   0,0  22:18.73 1 [irq/43-eth0]   
 4164 xxxxxxx   20     2188    432    360 S   5,0   0,0   0:03.52 1 /cache/writeloop --shared --block-size=768 --file-size=9216 /aa1 /aa2 /aa3   
 4170 xxxxxxx   20    42072  31564   3784 S   4,9   0,8   0:03.44 2 /cache/brutefir /cache/fromcache.conf -quiet   
 4163 xxxxxxx   20    42072  38536  10756 S   3,0   1,0   0:03.18 2 /cache/brutefir /cache/fromcache.conf -quiet    
Da laufen also `writeloop` und `bufhrt` auf dem gleichen Core, und dorthin habe ich auch die zwei Kernel-Interrupt Prozesse verschoben, die für das genaue Timing von `bufhrt` sorgen.

Viele Grüße,
Frank
Bild
Buschel
Aktiver Hörer
Beiträge: 989
Registriert: 12.12.2013, 20:12
Wohnort: Raum Karlsruhe

Beitrag von Buschel »

Hallo Stanislaw,
bastelixx hat geschrieben: 01.06.2020, 15:41Kannst du mit sicherheit sagen, dass die manuelle Festlegung für Prozessen-Priorität die Vorteile gegenüber der automatisierte Prozesssteuerung durch Sytem bringt? Hast du es ausprobiert? Sind die Latenzen nicht überbewertet.
Ich kann mit Sicherheit sagen, dass nach Festlegung der Prozesse auf Cores das System mit weniger Last läuft -- vermutlich, weil die Caches besser ausgenutzt werden können. Die alten Daten zeigen mir, dass die Last durch brutefir von etwa 8% auf etwa 6%, und die Last durch resample_soxr von 2,7% auf 2,1% gesunken war. Die Latenzen halte ich in meiner bisherigen Konfiguration deswegen für problematisch, weil es zu Aussetzern kommen konnte, und wohl auch kam. Mit Klang hat das erst einmal nix zu tun.
bastelixx hat geschrieben: 01.06.2020, 15:41Ein gewisses Komfort neben der Musikalität steht bei mir auch ganz oben auf der Liste.
Das kann ich 100%ig nachvollziehen. Bedienkomfort ist mir auch sehr wichtig. Wenn so eine pipe erst einmal definiert und angebunden ist, läuft alles wunderbar per remote von Smartphone oder Notebook aus.


Hallo Frank,
frankl hat geschrieben: 01.06.2020, 15:43 In Deiner Pipe würde ich mal probieren, das `sox` vor dem `playhrt` auf dem gleichen Core laufen zu lassen, denn das ist der kritischte Übergang.
Du würdest also kleinere (wenig Last verursachende), aus dem shared buffer lesende Prozesse auf denselben Core legen wie den schreibenden? Das sähe bei mir dann so aus (sox/resample_soxr auf Core 1, brutefir auf Core 2 und sox/playhrt auf Core 3):

Code: Alles auswählen

pcm.pipe_src96k_drc_dither_playhrt {
	type file
	file "	  | chrt -f 80 taskset -c 1 sox -D -v 0.8 -r 44.1k -b 32 -c 2 -e signed -t raw - -b 64 -c 2 -e float -t raw - \
		  | chrt -f 81 taskset -c 1 resample_soxr -c 2 -B 89 -P 50 -e 28 -i 44100 -o 96000 \
		  | chrt -f 82 taskset -c 2 brutefir -quiet -nodefault /home/buschel/Convolver/DRC_96k_stdio.conf \
		  | chrt -f 83 taskset -c 3 sox -t raw -r 96k -b 64 -c 2 -e float - -b 32 -c 2 -e signed -t raw - dither -p 24 \
		  | chrt -f 90 taskset -c 3 playhrt --stdin -k 2 -s 96000 -f S32_LE -d usbaudio --mmap --verbose --sleep=1000000"
	format "raw"
}
Umsortierte Prozesse

Laut "top" hast du auch brutefir, resample_soxr und den Ausgabe-Prozess (bufhrt) auf unterschiedliche Cores gelegt. War das ein Ergebnis deiner Experimente, geplantes Verteilen der Last oder auch ein Begrenzen der "Totzeit" auf jedem Core?

Viele Grüße,
Andree
Bild
bastelixx
Aktiver Hörer
Beiträge: 306
Registriert: 08.11.2015, 17:31

Beitrag von bastelixx »

frankl hat geschrieben: 01.06.2020, 15:43 [/code]

Mit `top` sieht man die Verteilung so (Spalte `P` ist die Affinität), die CPU-Zeiten schwanken nur so um +/-0.2%:

Code: Alles auswählen

  PID USER      PR     VIRT    RES    SHR S  %CPU  %MEM     TIME+ P COMMAND  
 4168 xxxxxxx   20    61532  58332  11060 R  73,1   1,4   0:52.93 2 /cache/brutefir /cache/fromcache.conf -quiet   
 4173 root     -61     2200    456    384 S  12,6   0,0   0:09.11 1 /cache/bufhrt --buffer-size=8192 --input-size=768 --bytes-per-second=1536000 --loops-per-second=1000 --overwrite=0 --port=5570 --extra-bytes-per-second=-3 --shared +  
 4162 xxxxxxx   20   221640   4280   2564 S  12,1   0,1   0:08.85 3 /cache/resample_soxr --buffer-length=3072 --shmname=/data.flac --outrate=192000 --band-width=85.0 --phase=13 --fading-length=150000 --param-file=/cache/VOLRACE192   
  542 root     -51        0      0      0 S   7,8   0,0  41:19.68 1 [irq/42-eth0]   
  543 root     -51        0      0      0 S   7,3   0,0  22:18.73 1 [irq/43-eth0]   
 4164 xxxxxxx   20     2188    432    360 S   5,0   0,0   0:03.52 1 /cache/writeloop --shared --block-size=768 --file-size=9216 /aa1 /aa2 /aa3   
 4170 xxxxxxx   20    42072  31564   3784 S   4,9   0,8   0:03.44 2 /cache/brutefir /cache/fromcache.conf -quiet   
 4163 xxxxxxx   20    42072  38536  10756 S   3,0   1,0   0:03.18 2 /cache/brutefir /cache/fromcache.conf -quiet    

Viele Grüße,
Frank
Hallo Frank,
ich bin von der Prozesspriorität deines BUFHRT beeindruckt -61. Mein MPD Prozess läuft mit -51. Ich bin nur über die BRUTEFIR Prozesse bei deinem Rechner nich ganz im Klaren. 20 ist kein RT Prozess, oder? Bei mir legt Brutefir mehrere Prozesse an: die Filter unter Brutefir laufen mit Prioritäten -6 und -7, werden unter HTOP so angezeigt

Gruß
Stanislaw
Bild
Antworten