Forum: PC-Programmierung USB CDC streaming und Python auf windows


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Mat. K. (matthias_kornfield)


Lesenswert?

Ich habe eine USB High Speed Board/projekt und will über USB ca 400kB/s 
streamen und es via python aufnehmen.
Ich kriege öfter missing packete.  Genaugenommen (siehe code unten)
A: mit time.sleep(0.1) klappt es
B: mit time.sleep(0.2) Bekomme ich USBD_BUSY in ST USB Library.

Auf der ST Seite habe ich leider keine memory um ein resend zu machen.
set_buffer_size() hilft auch nicht. Hat jemand eine Idee was ich sonst 
machen konnte?

import serial
import serial.tools.list_ports
....
mySerial = serial.Serial(port=port, baudrate=2000000, timeout= 1)
mySerial.set_buffer_size(rx_size=500000, tx_size=500000)

While(1):
 bytes_available = mySerial.in_waiting
 a = mySerial.read(bytes_available)
 time.sleep(0.2)

von Norbert (der_norbert)


Lesenswert?

Mat. K. schrieb:
> While(1):
>  bytes_available = mySerial.in_waiting
>  a = mySerial.read(bytes_available)
>  time.sleep(0.2)

Das ist, mit Verlaub gesagt, Unsinn.
›While‹ klein, Klammern weg, Bool Wert und vieles andere mehr…
1
#!/usr/bin/python3
2
# -*- coding: UTF-8 -*-
3
# vim: fileencoding=utf-8: ts=4: sw=4: expandtab:
4
import serial
5
import time
6
7
mySerial = serial.Serial(port='/dev/ttyACM0', baudrate=2000000, timeout=1)
8
while True:
9
    while (bytes_available := mySerial.in_waiting) <= 0:
10
        time.sleep(1/1000)
11
    data = mySerial.read(bytes_available)
12
    print(len(data))

Tatsächlich löst man diese einfache Aufgabe jedoch mittels select/poll.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> Tatsächlich löst man diese einfache Aufgabe jedoch mittels select/poll.

Ist das überhaupt nötig? Die read Funktion sollte doch einfach 
blockieren wenn keine Bytes verfügbar sind.

Allerdings sind die CDC-Treiber der Betriebssysteme nicht wirklich für 
solche Geschwindigkeiten ausgelegt.

von Norbert (der_norbert)


Lesenswert?

Niklas G. schrieb:
> Ist das überhaupt nötig? Die read Funktion sollte doch einfach
> blockieren wenn keine Bytes verfügbar sind.

Er hat einen Timeout angegeben.

Niklas G. schrieb:
> Allerdings sind die CDC-Treiber der Betriebssysteme nicht wirklich für
> solche Geschwindigkeiten ausgelegt.

Die CDC Treiber meines Betriebssystems schafft da noch deutlich mehr. 
DEUTLICH!

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> Er hat einen Timeout angegeben.

Dann ruft man "read" einfach nochmal auf bei Timeout.

Norbert schrieb:
> Die CDC Treiber meines Betriebssystems schafft da noch deutlich mehr.
> DEUTLICH!

Welches ist das?

von Mat. K. (matthias_kornfield)


Lesenswert?

Hi
ich teste es gerade auf Windows. Linux/mac folgen nocht.
Das problem ist das time.sleep(0.2 oder mehr)
Ohne den Sleep habe ich keine Probleme.
Dieser "sleep" ist place holder für ein dutzend Threads die auf dem PC 
noch laufen.

von Norbert (der_norbert)


Lesenswert?

Niklas G. schrieb:
> Welches ist das?

Das ist ein Debian GNU/Linux stable.
Von einem Discovery STM32F4 zum PC stellen sich geschmeidige 1MB/s ein. 
(Großes B)

Noch eines zum select/poll Ansatz:
Da ist die Nutzung vergleichbar mit dem Essen einer Suppe. Wenn man 
einmal von Gabel auf Löffel umgestiegen ist, möchte man nicht mehr 
zurück.

von Norbert (der_norbert)


Lesenswert?

Mat. K. schrieb:
> Dieser "sleep" ist place holder für ein dutzend Threads die auf dem PC
> noch laufen.

Aha. Ein Scheibchen Salami!
Hatte ich in diesem Zusammenhang schon select/poll und jetzt noch 
zusätzlich asyncio angesprochen?

von Mat. K. (matthias_kornfield)


Lesenswert?

Die anderen Threads interessieren erstmal nicht. Ich will in der Lage 
sein trotz time.sleep(0.2) mein stream hin zu bekommen. Verhält sich da 
pyusb besser?
wenn ich es richtig sehe
funktioniert das hier nicht:
mySerial.set_buffer_size(rx_size=500000, tx_size=500000)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Mat. K. schrieb:
> Die anderen Threads interessieren erstmal nicht

Die interessieren sehr wohl. Denn Python kann überhaupt kein echtes 
Multithreading sondern führt immer nur einen Thread aus (GIL). Wenn 
jetzt ein Thread in "read" blockiert kann kein anderer laufen. 
Prinzipiell sollte das bei IO-Funktionen "eigentlich" nicht passieren, 
aber wer weiß. Wenn ein anderer Thread den Python-Interpreter blockiert 
könnte das "read" gar nicht erst rechtzeitig aufgerufen werden.

Mat. K. schrieb:
> Verhält sich da
> pyusb besser?

Das ist grundsätzlich der bessere Ansatz, weil du da aus Python die 
Paketübertragung direkt steuern kannst. Heißt insbesondere, das Gerät 
kann gar keine Daten senden, bevor du den Transfer per pyusb 
angefordert hast, d.h. es können zwischen Gerät und Python-Anwendung 
keine Daten verloren gehen (anders als bei CDC). Allerdings musst du 
natürlich sicherstellen, dass die Datenrate immer hoch genug ist, damit 
du nicht auf dem Gerät einen Pufferüberlauf bekommst.

von Norbert (der_norbert)


Lesenswert?

Mat. K. schrieb:
> Ich will in der Lage
> sein trotz time.sleep(0.2) mein stream hin zu bekommen.

Tja, mit dem ›Wollen‹ ist das immer so eine Sache.
Man kann sich zu Weihnachten für einen Monat ins Bett legen wollen, darf 
sich dann aber nicht beschweren wenn man Silvester verpasst.

> Verhält sich da pyusb besser?

›Serial‹ verhält sich tadellos, wenn man es denn korrekt anwendet.

Lösungsvorschläge hast du, aber du machst das schon irgendwie…

von Norbert (der_norbert)


Lesenswert?

Niklas G. schrieb:
> Heißt insbesondere, das Gerät
> kann gar keine Daten senden, bevor du den Transfer per pyusb
> angefordert hast, d.h. es können zwischen Gerät und Python-Anwendung
> keine Daten verloren gehen (anders als bei CDC).

Er will kontinuierlich streamen und er hat auf dem Gerät keinen Platz 
für einen Puffer. Das macht's dann schon schwierig, wenn man kategorisch 
auf unsachgemäße Programmierung besteht.

von Jürgen (temp1234)


Lesenswert?

Niklas G. schrieb:
> Die interessieren sehr wohl. Denn Python kann überhaupt kein echtes
> Multithreading sondern führt immer nur einen Thread aus (GIL). Wenn
> jetzt ein Thread in "read" blockiert kann kein anderer laufen.
> Prinzipiell sollte das bei IO-Funktionen "eigentlich" nicht passieren,
> aber wer weiß. Wenn ein anderer Thread den Python-Interpreter blockiert
> könnte das "read" gar nicht erst rechtzeitig aufgerufen werden.

Was seit python 3.13.1 nicht mehr ganz stimmt. Man kann während der 
Installation auch den Haken machen bei "download free-threaded binaries" 
und ist den blockierenden GIL-Lock los.

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Jürgen schrieb:
> Was seit python 3.13.1 nicht mehr ganz stimmt. Man kann während der
> Installation auch den Haken machen bei "download free-threaded binaries"
> und ist den blockierenden GIL-Lock los.

Sollte man das nicht mögen, sondern mit dem Standard Python arbeiten 
wollen, so gibt es im Manual:
* im Allgemeinen die Sektion ›Concurrent Execution‹ und
* im Speziellen die Sektion ›subprocess‹

mitsamt passender Prozess Kommunikation.

von Mat. K. (matthias_kornfield)


Lesenswert?

zu GIL & multi threading im allgemeinen :
es gibt I/O-bound tasks und CPU-bound tasks. In case of USB CDC reden 
wir von I/O bound. Damit kommt python sehr gut zu recht: heisst der 
serial read is nicht blocking.
Darum geht es nicht.

von Jürgen (temp1234)


Lesenswert?

Norbert schrieb:
> Sollte man das nicht mögen

Man sollte sich aber langsam damit befassen, dass Python endlich 
erwachsen wird. Eine Programmiersprache die kein richtiges 
Multithreading beherrscht verdient es nicht als solche bezeichnet zu 
werden...
Nodejs ist auch so eine Krücke aber da ist das fehlende richtige 
Multithreading nur eines der Übel.

von Norbert (der_norbert)


Lesenswert?

Jürgen schrieb:
> Man sollte sich aber langsam damit befassen, dass Python endlich
> erwachsen wird.

Aaaaaaaaa…haaaaaaaaaaa.

Danke für die erleuchtende Information.

von Norbert (der_norbert)


Angehängte Dateien:

Lesenswert?

…und für diejenigen welche sich bis jetzt noch nicht Kopfschüttelnd 
abgewandt haben…
So sieht es aus wenn ein Standard Python Programm vier Prozesse auf vier 
Kernen der CPU ausführt. In … Moment … 19 Zeilen Code.
1
running on core:0
2
running on core:2
3
running on core:1
4
running on core:3
5
ended core:0
6
ended core:3
7
ended core:1
8
ended core:2

von Jürgen (temp1234)


Lesenswert?

Norbert schrieb:
> …und für diejenigen welche sich bis jetzt noch nicht Kopfschüttelnd
> abgewandt haben…
> So sieht es aus wenn ein Standard Python Programm vier Prozesse auf vier
> Kernen der CPU ausführt. In … Moment … 19 Zeilen Code.

Danke auch für die überaus erleuchtende Information die komplett 
nichssagend ist.
Wenn es keinen Handlungsbedarf an dieser Stelle gibt, müssen die Python 
Entwickler ja komplett hirnbefreit sein wenn sie sich mit sowas 
Unsinnigem beschäftigen.
Da gibt es auch noch andere Baustellen. Versuch mal Python in C++ oder 
Rust  multithreaded zu embedden. Also mit isoliertem GIL. Da wirst du 
schnell feststellen, dass große Teile der Libs noch nicht damit umgehen 
können. Ganz vorn dabei ctypes. Jedenfalls bis 3.12. In der 3.13 ist das 
jetzt behoben. Damit passt es dann wieder zur Aussage mit dem erwachsen 
werden.

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Jürgen schrieb:
> Wenn es keinen Handlungsbedarf an dieser Stelle gibt, müssen die Python
> Entwickler ja komplett hirnbefreit sein wenn sie sich mit sowas
> Unsinnigem beschäftigen.

Ich nehme mal zu deinen Gunsten an, dass du dich nur dumm stellst
Selbstverständlich kann und sollte man Python weiter verbessern. Das 
steht außer Frage.

Aber es ist schon jetzt völlig problemlos möglich in einem Python 
Programm alle verfügbaren Kerne gleichzeitig und somit einen ganzen 
Sack voller Prozesse zu nutzen.

Ganz im Gegensatz zu deiner kategorischen Aussage:
> Eine Programmiersprache die kein richtiges Multithreading beherrscht
> verdient es nicht als solche bezeichnet zu werden...

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> und somit einen ganzen
> Sack voller Prozesse zu nutzen.
>
> Ganz im Gegensatz zu deiner kategorischen Aussage:
>> Eine Programmiersprache die kein richtiges Multithreading beherrscht
>> verdient es nicht als solche bezeichnet zu werden...

Multithreading != Multiprocessing

Norbert schrieb:
> Er will kontinuierlich streamen und er hat auf dem Gerät keinen Platz
> für einen Puffer.

Eine gewisse Menge Puffer hat man immer. Ein STM32 mit USB High-Speed 
hat vermutlich genug SRAM um für mindestens 100ms zu puffern, außer das 
ist alles schon belegt...

Ich würde sowas mit libUSB und nativem USB-Protokoll in C++ machen. 
Obwohl sowas grundsätzlich auch mit asynchronem IO geht ist der Support 
dafür eher stiefmütterlich, daher würd ich das libusb_bulk_transfer in 
einer Schleife in einem dedizierten Thread aufrufen (C++ hat natürlich 
echte Threads).

von Norbert (der_norbert)


Lesenswert?

Niklas G. schrieb:
> Multithreading != Multiprocessing

Das ist richtig. Andererseits werden zum Beispiel auf Linux System gerne 
LWPs genutzt. Und schon verschwimmen die Grenzen.
Im Übrigen ist - ebenfalls bei Linux - die Erstellung eines Prozesses 
erstaunlich Kostengünstig (CPU-Zeit).
Und wenn man nicht ständig Zettabytes an Daten zwischen den Prozessen 
hin und her schiebt, dann gibt es kaum (im Sinne von fast keine) 
Nachteile.

Aber um mal wieder den Bogen zu diesem Thread (no pun intended) zu 
schlagen, alle ›scheinbaren‹ Probleme mit dem Datenempfang ließen sich 
mit sehr wenigen Zeilen Code lösen.
Und ein einziges mal einen Prozess unter Windows zu generieren kann man 
CPU-Zeit mäßig sicherlich verschmerzen.
Und man bräuchte sich danach noch nicht einmal um irgendwelche 
sleep/delay Unsauberheiten zu kümmern. Ein paar hundert KB/s, auch ein 
paar MB/s eingehend sind wirklich Kinderkram.

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Norbert schrieb:

> Im Übrigen ist - ebenfalls bei Linux - die Erstellung eines Prozesses
> erstaunlich Kostengünstig (CPU-Zeit).

Auch wenn die Erstellung eines Prozesses vergleichsweise günstig 
erfolgen kann, ist und bleibt es Schwachsinn, einen solchen zu erzeugen 
ohne jede Not.

Und diese Not kann nur eine sein: Eine GEWÜNSCHTE Isolation der Daten 
zwischen den Prozessen. Das genau ist aber hier gerade nicht gewünscht, 
sondern im Gegenteil vollständige Teilung mit minimalem negativen 
Performance-Impact.
Also: klare Aufgabe für Threads.

von Jürgen (temp1234)


Lesenswert?

Ob S. schrieb:
> Auch wenn die Erstellung eines Prozesses vergleichsweise günstig
> erfolgen kann, ist und bleibt es Schwachsinn, einen solchen zu erzeugen
> ohne jede Not.
>
> Und diese Not kann nur eine sein: Eine GEWÜNSCHTE Isolation der Daten
> zwischen den Prozessen.

Danke. Genau so sieht es aus. Und wenn man auf gemeinsame Daten in 
verschiedenen Threads angewiesen ist, dann ist es erst ab Python 3.13 
überhaupt sinnvoll. So lange ist es auch noch nicht her dass es 
überhaupt isolierte Interpreter in Python gibt. Etwas was PHP seit 
vielen Jahren von Anfang an beherrscht hat. Auch nodejs mit der V8 hat 
sich da lange schwer getan. Aber genug damit, der Threadstarter hatte 
andere Probleme.

von Harald K. (kirnbichler)


Lesenswert?

Ob S. schrieb:
> Also: klare Aufgabe für Threads.

Früher(tm) war halt fork das einzig wahre. Ein echtes Unix kannte 
keine Threads.

von Norbert (der_norbert)


Lesenswert?

Nun Gentlemen, da sind wir doch noch zu einem für alle 
zufriedenstellenden Ergebnis gekommen. Wieder einmal zeigt sich, man 
muss nur miteinander reden.

Halten wir fest: Die Einen können die gestellte Aufgabe mittels Python 
lösen, die Anderen eben nicht. Dafür verfechten sie jedoch die reine 
Lehre, was auch gut ist. Muss mal erwähnt werden.

Ich hab's gerade mal aus reinem Spaß an der Freude auf einem >20 Jahre 
alten Pentium III(coppermine / single core) getestet.

Da dauert die (nur einmalig erforderliche) Erstellung eines Prozesses 
tatsächlich unerträglich lang. Ich habe dennoch die vollen 22 
Millisekunden abgewartet und danach lief das Programm prima.

Bedankt für die wertvolle Diskussion. ;)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> Da dauert die (nur einmalig erforderliche) Erstellung eines Prozesses
> tatsächlich unerträglich lang.

Und wie lange dauert das Rüberschaufeln eines Datenblocks vom 
USB-Prozess in den Hauptprozess? Wie lange wird die Empfangsschleife 
dabei blockiert?

von Norbert (der_norbert)


Lesenswert?

Niklas G. schrieb:
> Und wie lange dauert das Rüberschaufeln eines Datenblocks vom
> USB-Prozess in den Hauptprozess? Wie lange wird die Empfangsschleife
> dabei blockiert?

Shared memory, rate doch mal…

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> Shared memory, rate doch mal…

Dann rate ich 2.7 Sekunden, d.h. zu lange.

von Norbert (der_norbert)


Lesenswert?

Niklas G. schrieb:
> Norbert schrieb:
>> Shared memory, rate doch mal…
>
> Dann rate ich 2.7 Sekunden, d.h. zu lange.

Nein, nein. Eine kurze ›Fertig1/Fertig2‹ Message rüber senden und direkt 
auf den Speicher zugreifen, da braucht's eher 2.7 Tage. Und das ist noch 
konservativ geschätzt.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> Message rüber senden

Wie sendest du die Messsage? Was ist das für ein 
Synchronisationsprimitiv? Kann der Empfänger den Sender blockieren...?

von Norbert (der_norbert)


Lesenswert?

Event. Ja (soll er aber ausdrücklich nicht)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> Event

Und wie lange dauert es damit? Welches Primitiv des OS wird da genutzt? 
Wie groß ist die Latenz der diversen Kontextwechsel?

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Niklas G. schrieb:
> Und wie lange dauert es damit?

Aus dem Kopf: Einige tausend pro Sekunde. Auf einem fünfzehn Jahre alten 
dual core Intel Core i3-4010U.

Den Rest darfst du aber gerne selbst testen. Python ist kostenlos. 
Dokumentation ist kostenlos. Rechner setze ich voraus. ;-)

Aber bitte denke immer daran, das ist alles verbotenes Teufelszeug!

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> Aber bitte denke immer daran, das ist alles verbotenes Teufelszeug!

Ich hab doch nur gefragt...

von Ein T. (ein_typ)


Lesenswert?

Norbert schrieb:
> Jürgen schrieb:
>> Was seit python 3.13.1 nicht mehr ganz stimmt. Man kann während der
>> Installation auch den Haken machen bei "download free-threaded binaries"
>> und ist den blockierenden GIL-Lock los.
>
> Sollte man das nicht mögen,

... zum Beispiel, weil das Feature noch "experimental" ist ...

> sondern mit dem Standard Python arbeiten
> wollen, so gibt es im Manual:
> * im Allgemeinen die Sektion ›Concurrent Execution‹ und
> * im Speziellen die Sektion ›subprocess‹
>
> mitsamt passender Prozess Kommunikation.

Daß das Modul "multiprocessing" eine sehr ähnliche API wie "threading" 
hat, sollte an dieser Stelle lobend erwähnt werden. Diese Ähnlichkeit 
macht es  einfach, von Threading auf Multiprocessing zu wechseln. :-)

von Ein T. (ein_typ)


Lesenswert?

Ob S. schrieb:
> Auch wenn die Erstellung eines Prozesses vergleichsweise günstig
> erfolgen kann, ist und bleibt es Schwachsinn, einen solchen zu erzeugen
> ohne jede Not.
>
> Und diese Not kann nur eine sein: Eine GEWÜNSCHTE Isolation der Daten
> zwischen den Prozessen. Das genau ist aber hier gerade nicht gewünscht,
> sondern im Gegenteil vollständige Teilung mit minimalem negativen
> Performance-Impact.
> Also: klare Aufgabe für Threads.

So eine Auswahl treffen nur Laien anhand ihrer WÜNSCHE.  Profis 
orientieren sich stattdessen vielmehr daran, was NÖTIG ist.

Wer einen "minimalen negativen Performance-Impact" braucht, entwickelt 
doch nicht in einer interpretierten Skriptsprache. Der benutzt einen 
optimierten Interpreter wie Pypy oder IronPython. Der nutzt 
Bibliotheken, die, wie numpy und etliche andere, ohnehin nicht vom GIL 
betroffen sind. Oder er entwickelt die performancekritischen Teile 
seiner Software in einer anderen Sprache mit Python.h (C), Boost::Python 
(C++), PyO3 (Rust) oder Cgo (Golang). Oooder er verwendet einen der 
verfügbaren Python-Compiler wie nuitka oder numba. Alle diese 
Möglichkeiten existieren, obwohl ein "minimaler negativer 
Performance-Impact" für die allermeisten Programme ohnehin gar nicht 
notwendig ist.

von Norbert (der_norbert)


Lesenswert?

Niklas G. schrieb:
> Und wie lange dauert es damit? Welches Primitiv des OS wird da genutzt?
> Wie groß ist die Latenz der diversen Kontextwechsel?

Hab' jetzt mal mit der wirklich schlechtest möglichen 0815 plain Jane 
Herangehensweise und völlig ohne Optimierungen getestet.
Da serielle Schnittstellen zum Testen selbstverständlich viel zu 
lahmarschig sind, sendet ein Prozess statt dessen mit maximaler 
Geschwindigkeit 10000 Blöcke (bytearrays) (jeweils mit unterschiedlicher 
Größe.)
Die werden bei der Übergabe an den Empfänger-Prozess heftigst im RAM 
herum kopiert. Der zweite Prozess nimmt die Daten jeweils komplett an 
und verwirft sie dann.

Auf einem alten Desktop Rechner:
1
inxi -C
2
CPU:       Info: Quad Core model: Intel Core i5-3470 bits: 64 type: MCP L2 cache: 6 MiB 
3
           Speed: 1689 MHz min/max: 1600/3600 MHz

ergeben sich je nach Blockgröße:
1
    2^14:  744.6 MB/s
2
    2^15: 1193.0 MB/s
3
    2^16: 1267.7 MB/s
4
    2^17: 1724.1 MB/s
5
    2^18:  885.4 MB/s

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> ergeben sich je nach Blockgröße:

Der Durchsatz ist schonmal gut, aber viel wichtiger ist hier die Latenz, 
genauer die Dauer der Sende-Operation. Sonst wird der Serial/USB-Thread 
zu lange blockiert.

von Norbert (der_norbert)


Lesenswert?

Niklas G. schrieb:
> Norbert schrieb:
>> ergeben sich je nach Blockgröße:
>
> Der Durchsatz ist schonmal gut, aber viel wichtiger ist hier die Latenz,
> genauer die Dauer der Sende-Operation. Sonst wird der Serial/USB-Thread
> zu lange blockiert.

Ähm, nein, wird er nicht. Erstens und zweitens hat der OS-Treiber einen 
eigenen Buffer und läuft völlig unabhängig. Man hat also Zeit die Daten 
abzuholen. Hier reden wir von einigen 100µs.
Nee … Moment … ich mess' mal … fast immer deutlich weniger:
1
average: 14.4 µs
2
Transfer (2^10): 57.5 MB/s
3
average: 15.2 µs
4
Transfer (2^11): 109.9 MB/s
5
average: 16.1 µs
6
Transfer (2^12): 210.1 MB/s
7
average: 18.0 µs
8
Transfer (2^13): 382.3 MB/s
9
average: 22.6 µs
10
Transfer (2^14): 621.7 MB/s
11
average: 29.1 µs
12
Transfer (2^15): 987.2 MB/s
13
average: 53.9 µs
14
Transfer (2^16): 1113.2 MB/s
15
average: 100.6 µs
16
Transfer (2^17): 1230.1 MB/s
17
average: 343.7 µs
18
Transfer (2^18): 747.6 MB/s

Transferraten etwas geringer, da tausende von Messdaten während der 
Übertragung gespeichert werden müssen.

von Norbert (der_norbert)


Lesenswert?

Niklas G. schrieb:
> Der Durchsatz ist schonmal gut,

Das ist vermutlich der schlechtest Mögliche. Mit weiteren zehn bis 
zwanzig Zeilen Code würde ich eine Erhöhung um eine Größenordnung 
erwarten.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> Ähm, nein, wird er nicht.

Wie funktioniert das dann? Wie kann man Daten abschicken während 
gleichzeitig eine "read" Operation läuft?

Norbert schrieb:
> und zweitens hat der OS-Treiber einen
> eigenen Buffer

Bei CDC ja, bei direktem USB-Zugriff (z.B. pyusb) nicht. Gerade unter 
Windows funktioniert dieser Puffer nicht so toll und verschluckt gerne 
mal Daten.

Norbert schrieb:
> Hier reden wir von einigen 100µs.

Das passt, dann ist die Frage geklärt.

von Norbert (der_norbert)


Lesenswert?

Bei USB LS und FS muss man noch nicht einmal darüber nachdenken.
Und selbst bei USB HS HID werden kaum mehr als 8MB/s** anfallen.


** 1000ms/s × 8 microframes/ms × 1024 Bytes/microframe
(Falls ich das recht erinnere)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> Bei USB LS und FS muss man noch nicht einmal darüber nachdenken.

Mit genug Overhead und schlechter Programmierung kann man selbst 
langsame Übertragungen durcheinander bringen. Das ist ja selbst bei 
Zugriffen innerhalb des Computers so, wenn man z.B. einzelne Bytes in 
den RAM oder die Konsole schreibt, obwohl es Caches/Puffer gibt.

von Norbert (der_norbert)


Lesenswert?

Niklas G. schrieb:
> Mit genug Overhead und schlechter Programmierung kann man selbst
> langsame Übertragungen durcheinander bringen.

Da hilft dann nur noch striktes Tastaturverbot und zurück schicken zum 
Teig kneten…

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> und zurück schicken zum
> Teig kneten…

Da kann man auch genug falsch machen!

von Rolf M. (rmagnus)


Lesenswert?

Harald K. schrieb:
> Ob S. schrieb:
>> Also: klare Aufgabe für Threads.
>
> Früher(tm) war halt fork das einzig wahre. Ein echtes Unix kannte
> keine Threads.

Früher(tm) war fork das einzig verfügbare. Hätte es Threads gegeben, 
dann hätte man sie auch genutzt.
Das ist aber eh alles schon lange her. Der pthreads-Standard wurde vor 
30 Jahren verabschiedet, ein Jahr später bekam Linux eine erste Variante 
von Thread-Support. Dass Python das bis heute nicht sauber hinbekommen 
hat, ist da nicht gerade glorreich.

von Norbert (der_norbert)


Lesenswert?

Rolf M. schrieb:
> fork

Verrückt, oder? Gibt's seit gut fünfzig Jahren, ist FOSS, darf also 
jeder benutzen, wird viele hundert Millionen Mal am Tag genutzt und es 
funktioniert immer noch für eine sehr große Anzahl an Aufgaben absolut 
tadellos.

Das nenne ich mal Nachhaltig. ;-)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Norbert schrieb:
> Das nenne ich mal Nachhaltig. ;-)

Naja, es wurde schon einiges dran rumoptimiert damit es weiterhin gut 
funktioniert, insbesondere das mit dem Copy-On-Write der Pages. Aber ja, 
es ist schon ein durchdachtes API.

Unter Windows könnte man es mit WinUSB und Overlapped IO besonders 
effizient asynchron hinbekommen, unter Linux vermutlich mit epoll oder 
so. Braucht dann nur 1 Thread/Prozess für alles I/O. Ist hier aber wohl 
eher unnötig.

von Norbert (der_norbert)


Lesenswert?

Norbert schrieb:
> Mit weiteren zehn bis
> zwanzig Zeilen Code würde ich eine Erhöhung um eine Größenordnung
> erwarten.

Da habe ich ja verdammt gut geraten.
1
Transfer (2^10):   178.0 MB/s
2
Transfer (2^11):   350.3 MB/s
3
Transfer (2^12):   653.5 MB/s
4
Transfer (2^13):  1288.3 MB/s
5
Transfer (2^14):  2691.5 MB/s
6
Transfer (2^15):  4900.8 MB/s
7
Transfer (2^16):  7501.1 MB/s
8
Transfer (2^17):  9693.2 MB/s
9
Transfer (2^18): 11676.4 MB/s
10
Transfer (2^19): 13308.0 MB/s
11
Transfer (2^20): 12880.8 MB/s

Selbstverständlich mit Synchronisation/Signalisierung zwischen den 
Prozessen.
Proc1 schreibt, Proc2 wartet bis fertig.
Proc2 liest, Proc1 wartet bis fertig.
Jetzt ist aber genug, 13GB/s sollten für die Daten einer USB 
Kommunikation mehr als ausreichend sein. ;-)

Waren übrigens weniger als zehn Zeilen, da hatte ich schlecht geraten.

von Ein T. (ein_typ)


Lesenswert?

Rolf M. schrieb:
> Das ist aber eh alles schon lange her. Der pthreads-Standard wurde vor
> 30 Jahren verabschiedet, ein Jahr später bekam Linux eine erste Variante
> von Thread-Support. Dass Python das bis heute nicht sauber hinbekommen
> hat, ist da nicht gerade glorreich.

Nunja, die Referenzimplementierung von CPython war nie und ist immer 
noch nicht performanceoptimiert. Wer seinen Python-Code performanter 
haben will, verwendet einfach einen anderen Interpreter, zum Beispiel 
Pypy mit seinem Just-in-Time-Compiler. Das GIL dient einer einfachen 
Synchronisierung beim Zugriff mehrerer Threads auf dieselben Daten. Aber 
wo mehrere Prozesse oder Threads auf dieselben Speicherobjekte zugreifen 
sollen, sind doch derartige Synchronisierungen ohnehin zwingend 
notwendig.

Außerdem gibt es schließlich Alternativen zum Multithreading, namentlich 
das Modul "multiprocessing" anstelle des "treading"-Moduls. Das 
"multiprocessing"-Modul hat nicht nur eine ähnliche API, die es zur 
Ersetzung von "threading" empfehlen, sondern bietet auch noch viele 
weiterer sinnvoller Features zur Interprozesskommunikation, 
Synchronisierung, Pooling, und so weiter.

Nur so rein spaßeshalber habe ich mir mal ein paar kleine 
Testprogrämmchen geschrieben, die Ganzzahlen auf der Basis eines 
Startwertes 100 Millionen Mal inkrementieren und aus der Summe jeweils 
die Wurzel ziehen. Ein Programm nutzt das Modul "threading", eines 
"multiprocessing" und das letzte macht dasselbe mit Numpy, und die 
Programme wurden je einmal mit Python in Version 3.12.3 und mit Pypy 
Version 3.9.18 auf einem Intel Core i7-7820HQ ausgeführt. (Nein, das ist 
kein richtiger Benchmark, nur eine schnelle Spielerei.)

Die Ergebnisse sind fast genau so, wie ich es erwartet hatte: mit 
CPython und "threading", also dem GIL, benötigt das Programm ca. 196 
Sekunden, mit Pypy und demselben Modul nur noch 9,5 Sekunden. Mit 
"multiprocessing" und CPython benötigt das Programm hingegen nurmehr 
55,2 Sekunden, wohingegen dasselbe Programm mit Pypy nur 2,0 Sekunden 
benötigt.

Die Verwendung des "multiprocessing"- anstelle des "threading"-Moduls 
sorgt daher für eine Beschleunigung von "nur" Faktor 3,5 -- nicht sehr 
viel, aber immerhin doch recht deutlich. Viel mehr bringt die Verwendung 
eines anderen Interpreters, nämlich im Fall von "threading" um den 
Faktor 20 und im Falle von "multiprocessing" sogar um Faktor 27. Wer 
sowohl "multiprocessing" als auch Pypy benutzt, beschleunigt die ganze 
Veranstaltung somit um den Faktor 98(!) -- und das mit minimalen 
Änderungen am Code.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.