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)
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.
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.
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!
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?
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.
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.
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?
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)
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.
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…
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.
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
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.
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.
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.
Jürgen schrieb: > Man sollte sich aber langsam damit befassen, dass Python endlich > erwachsen wird. Aaaaaaaaa…haaaaaaaaaaa. Danke für die erleuchtende Information.
…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 |
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
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...
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).
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.
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.
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.
Ob S. schrieb: > Also: klare Aufgabe für Threads. Früher(tm) war halt fork das einzig wahre. Ein echtes Unix kannte keine Threads.
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. ;)
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?
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…
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.
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...?
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
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
Norbert schrieb: > Aber bitte denke immer daran, das ist alles verbotenes Teufelszeug! Ich hab doch nur gefragt...
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. :-)
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.
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 |
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.
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.
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.
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.
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)
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.
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…
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.
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. ;-)
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.
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.