HyperAI
Back to Headlines

Frei-Threaded Python verdoppelt Leistung bei DataFrame-Bearbeitung

vor 2 Tagen

Die Freigabe der Leistung durch unveränderliche DataFrames im freigewordenen Python Die Anwendung von Funktionen auf jede Zeile eines DataFrames ist eine übliche Operation. Diese Operationen sind peinlich parallelisierbar, da jede Zeile unabhängig voneinander verarbeitet werden kann. Mit einem Mehrkern-CPU können viele Zeilen gleichzeitig bearbeitet werden. Bis vor kurzem war die Ausnutzung dieser Möglichkeiten in Python nicht möglich. CPU-intensiv betriebene mehrfädige Funktionenanwendungen wurden durch das Global Interpreter Lock (GIL) verlangsamt. Python bietet nun eine Lösung: mit dem "experimentellen freifädigen Build" von Python 3.13 wird das GIL entfernt, und echte mehrfädige Parallelität für CPU-intensive Operationen ist möglich. Die Leistungsverbesserungen sind außergewöhnlich. Durch die Nutzung des freifädigen Pythons kann StaticFrame 3.2 Zeilenweise Funktionenanwendungen auf DataFrames mindestens doppelt so schnell durchführen wie bei einfädiger Ausführung. Zum Beispiel kann die Summe aller geraden Werte in einer Million-Zeilen-Quadrat-DataFrame (mit ganzen Zahlen) mit einer Lambda-Funktion berechnet werden: lambda s: s.loc[s % 2 == 0].sum(). Bei der Verwendung von Python 3.13t (das "t" steht für den freifädigen Varianten) sinkt die Dauer (gemessen mit %timeit in IPython) um mehr als 60%, von 21,3 ms auf 7,89 ms: ```python Python 3.13.5 experimenteller freifädiger Build (main, 11. Juni 2025, 15:36:57) [Clang 16.0.0 (clang-1600.0.26.6)] unter macOS import numpy as np; import static_frame as sf f = sf.Frame(np.arange(1_000_000).reshape(1000, 1000)) func = lambda s: s.loc[s % 2 == 0].sum() %timeit f.iter_series(axis=1).apply(func) 21,3 ms ± 77,1 µs pro Schleife (Mittel ± Standardabweichung von 7 Durchläufen, 10 Schleifen jeweils) %timeit f.iter_series(axis=1).apply_pool(func, use_threads=True, max_workers=4) 7,89 ms ± 60,1 µs pro Schleife (Mittel ± Standardabweichung von 7 Durchläufen, 100 Schleifen jeweils) ``` Zeilenweise Funktionenanwendungen in StaticFrame verwenden die iter_series(axis=1)-Schnittstelle, gefolgt von entweder apply() (für einfädige Anwendungen) oder apply_pool() (für mehrfädige Anwendungen, wobei use_threads=True für Threads und use_threads=False für Prozesse verwendet wird). Die Vorteile der Nutzung des freifädigen Pythons sind robust: die Überlegenheit ist konsistent bei verschiedenen DataFrame-Formen und -Zusammenstellungen und skaliert positiv mit der Größe des DataFrames, sowohl unter macOS als auch unter Linux. Allerdings gibt es Handelsspannen bei der Nutzung des freifädigen Pythons: wie in den Beispielen ersichtlich, ist einfadiges Processing langsamer (21,3 ms bei 3.13t im Vergleich zu 17,7 ms bei 3.13). Der freifädige Python führt generell Leistungsüberlastungen mit sich. Dies ist ein aktives Entwicklungsgebiet in CPython, und Verbesserungen werden in Version 3.14t erwartet. Darüber hinaus bieten viele C-Erweiterungspakete wie NumPy nun vorab komilierte binäre Räder für 3.13t, aber Risiken wie Threadkonflikte oder Datenrennen bestehen weiterhin. StaticFrame minimiert diese Risiken durch die Einhaltung der Unveränderlichkeit: Thread-Sicherheit ist implizit, da keine Sperren oder defensiven Kopien erforderlich sind. StaticFrame erreicht dies durch die Verwendung unveränderlicher NumPy-Arrays (mit flags.writeable auf False gesetzt) und die Verboten von in-situ-Mutationen. Erweiterte DataFrame-Leistungstests Die Bewertung der Leistungsmerkmale einer komplexen Datenstruktur wie einem DataFrame erfordert eine Vielzahl von Testtypen. Die folgenden Leistungstests führen Zeilenweise Funktionenanwendungen auf neun verschiedene DataFrame-Typen durch, wobei alle Kombinationen von drei Formen und drei Grad der Typ-Homogenität getestet werden. Für eine feste Anzahl von Elementen (z.B. 1 Million) werden drei Formen getestet: hoch (10.000 × 100), quadratisch (1.000 × 1.000) und breit (100 × 10.000). Um die Typ-Homogenität zu variieren, sind drei Kategorien synthetischer Daten definiert: spaltenweise (keine benachbarten Spalten haben denselben Datentyp), gemischt (Gruppen von vier benachbarten Spalten teilen denselben Datentyp) und uniform (alle Spalten haben denselben Datentyp). StaticFrame ermöglicht die Darstellung benachbarter Spalten desselben Typs als zweidimensionale NumPy-Arrays, was die Kosten für Spaltenübergänge und Zeilenerzeugung reduziert. Bei vollständiger Homogenität kann ein gesamtes DataFrame als ein zweidimensionales Array dargestellt werden. Synthetische Daten werden mit dem Paket frame-fixtures generiert. Die gleiche Funktion wird verwendet: lambda s: s.loc[s % 2 == 0].sum(). Obwohl eine effizientere Implementierung direkt mit NumPy möglich wäre, approximiert diese Funktion typische Anwendungen, bei denen viele Zwischenergebnisse erstellt werden. Wie unten gezeigt, sind die Leistungsverbesserungen durch mehrfädiges Processing in 3.13t bei allen getesteten DataFrame-Typen konsistent: die Verarbeitungszeit wird um mindestens 50% reduziert und in einigen Fällen sogar um über 80%. Für hochgezogene DataFrames ist die optimale Anzahl von Threads (der Parameter max_workers) kleiner, da die schnellere Verarbeitung kleinerer Zeilen bedeutet, dass zusätzliche Thread-Überlastungen tatsächlich die Leistung verschlechtern. Bei DataFrames mit 100 Millionen Elementen (1e8) verbessern sich die Überlegenheiten weiter. Die Verarbeitungszeit wird um über 70% reduziert, bis auf zwei DataFrame-Typen. Die Mehrfädigen-Überlastungen können zwischen Plattformen stark variieren. In allen Fällen bleibt die relative Überlegenheit der Nutzung des freifädigen Pythons konsistent zwischen macOS und Linux, wobei macOS marginal größere Vorteile zeigt. Bei der Verarbeitung von 100 Millionen Elementen unter Linux zeigt sich eine ähnliche relative Überlegenheit. Überraschenderweise profitieren даже kleine DataFrames mit nur zehntausend Elementen (1e4) von mehrfädigem Processing in 3.13t. Obwohl breite DataFrames keinen Vorteil zeigen, kann die Verarbeitungszeit von hochgezogenen und quadratischen DataFrames halbiert werden. Mehrprozessiges Funktionenanwenden mit Standard Python 3.13 Bevor freifädiger Python verfügbar war, führte mehrfädiges Processing von CPU-intensiven Anwendungen zu einer Verschlechterung der Leistung. Dies wird in den folgenden Tests deutlich, bei denen das gleiche Setup mit Standard Python 3.13 verwendet wird. Mehrprozessige Zeilenweise Funktionenanwendungen verschlechtern die Leistung erheblich, wobei die Prozessdauer sich von zwei- bis zehnfach gegenüber der einfadigen Dauer erhöht. Jede Arbeitseinheit ist zu klein, um die hohen Kosten der Erstellung eines Interpreters pro Prozess und das Kopieren von Daten zwischen Prozessen auszugleichen. Der Status des freifädigen Pythons PEP 703, "Entfernen des Global Interpreter Locks in CPython", wurde im Juli 2023 vom Python Steering Council akzeptiert. Es wurde angeordnet, dass im ersten Phase (für Python 3.13) das Projekt experimentell und optional sein sollte, im zweiten Phase nicht-experimentell und offiziell unterstützt, und im dritten Phase zur Standard-Python-Implementierung werden soll. Nach erheblicher CPython-Entwicklung und Unterstützung durch wichtige Pakete wie NumPy wurde PEP 779, "Kriterien für die unterstützte Status des freifädigen Pythons", im Juni 2025 vom Python Steering Council akzeptiert. In Python 3.14 wird der freifädige Python die zweite Phase erreichen: er wird nicht-experimentell und offiziell unterstützt sein. Ob und wann der freifädige Python der Standard wird, ist noch unklar, aber es ist deutlich, dass der Weg dafür geebnet ist. Fazit Zeilenweise Funktionenanwendungen sind nur der Anfang: Gruppierungsoperationen, fensterbasierte Funktionenanwendungen und viele andere Operationen auf unveränderlichen DataFrames sind gleichfalls für die parallele Ausführung geeignet und werden vergleichbare Leistungsverbesserungen aufweisen. Die Bemühungen, CPython zu beschleunigen, haben Erfolg gehabt: Python 3.14 soll 20% bis 40% schneller sein als Python 3.10. Leider haben diese Leistungsverbesserungen sich für viele, die mit DataFrames arbeiten, nicht eingestellt, da die Leistung oft in C-Erweiterungsbibliotheken (wie NumPy, Arrow oder anderen) gebunden ist. Wie hier gezeigt, ermöglicht der freifädige Python eine effiziente parallele Ausführung durch günstige und speichereffiziente Threads, wodurch die Verarbeitungszeit um 50% bis 90% reduziert wird, sogar wenn die Leistung primär in C-Erweiterungsbibliotheken wie NumPy gebunden ist. Durch die Möglichkeit, unveränderliche Datenstrukturen sicher über Threads zu teilen, bieten sich nun zahlreiche Möglichkeiten für substantielle Leistungsverbesserungen. Industrielle Insider loben die Entwicklung des freifädigen Pythons als einen entscheidenden Schritt zur Erhöhung der Leistung in Data-Spezialisierungsbibliotheken. Die Unterstützung durch wichtige Pakete wie NumPy und andere zeigt, dass die Community hinter diesem Fortschritt steht. StaticFrame, mit seiner Fokus auf Thread-Sicherheit und Effizienz, ist ein führendes Beispiel für die Vorteile, die freifädiger Python bieten kann.

Related Links