Multicore Python: Ein hartes, würdiges und erreichbares Ziel

Bei allen großartigen und praktischen Funktionen von Python bleibt ein Ziel unerreichbar: Python-Apps, die auf dem CPython-Referenzinterpreter ausgeführt werden und mehrere CPU-Kerne parallel verwenden.

Dies ist seit langem einer der größten Stolpersteine ​​von Python, zumal alle Problemumgehungen ungeschickt sind. Die Dringlichkeit, eine langfristige Lösung für das Problem zu finden, wächst, insbesondere da die Anzahl der Prozessoren immer weiter zunimmt (siehe Intels 24-Kern-Gigant).

Ein Schloss für alle

In Wahrheit ist es möglich, Threads in Python-Anwendungen zu verwenden - viele von ihnen tun dies bereits. Was  nicht möglich ist, ist, dass CPython Multithread-Anwendungen ausführt, wobei jeder Thread parallel auf einem anderen Kern ausgeführt wird. Die interne Speicherverwaltung von CPython ist nicht threadsicher, daher führt der Interpreter jeweils nur einen Thread aus, wechselt bei Bedarf zwischen diesen und steuert den Zugriff auf den globalen Status.

Dieser Sperrmechanismus, die Global Interpreter Lock (GIL), ist der Hauptgrund, warum CPython keine Threads parallel ausführen kann. Es gibt einige mildernde Faktoren; Beispielsweise sind E / A-Vorgänge wie Festplatten- oder Netzwerklesevorgänge nicht an die GIL gebunden, sodass diese in ihren eigenen Threads frei ausgeführt werden können. Aber alles, was sowohl Multithreading als auch CPU-gebunden ist, ist ein Problem.

Für Python-Programmierer bedeutet dies, dass schwere Rechenaufgaben, die von der Verteilung auf mehrere Kerne profitieren, nicht gut ausgeführt werden, sofern keine externe Bibliothek verwendet wird. Die Bequemlichkeit der Arbeit in Python ist mit erheblichen Leistungskosten verbunden, die immer schwerer zu schlucken sind, da schnellere, ebenso bequeme Sprachen wie Googles Go in den Vordergrund treten.

Ein Schloss knacken

Im Laufe der Zeit haben sich eine Reihe von Optionen herausgebildet, die die Grenzen der GIL verbessern, aber nicht beseitigen. Eine Standardtaktik besteht darin, mehrere Instanzen von CPython zu starten und Kontext und Status zwischen ihnen zu teilen. Jede Instanz wird unabhängig von der anderen in einem separaten Prozess ausgeführt. Wie Jeff Knupp erklärt, können die durch das parallele Laufen erzielten Gewinne durch den Aufwand für die gemeinsame Nutzung des Status verloren gehen. Daher eignet sich diese Technik am besten für langfristige Operationen, bei denen die Ergebnisse im Laufe der Zeit zusammengefasst werden.

C-Erweiterungen sind nicht an die GIL gebunden, sodass viele Bibliotheken für Python, die Geschwindigkeit benötigen (z. B. die Mathematik- und Statistikbibliothek Numpy), über mehrere Kerne ausgeführt werden können. Die Einschränkungen in CPython selbst bleiben jedoch bestehen. Wenn der beste Weg, um die GIL zu vermeiden, die Verwendung von C ist, werden mehr Programmierer von Python weg und in Richtung C getrieben.

PyPy, die Python-Version, die Code über JIT kompiliert, entfernt die GIL nicht, sondern gleicht dies aus, indem Code einfach schneller ausgeführt wird. In mancher Hinsicht ist dies kein schlechter Ersatz: Wenn Geschwindigkeit der Hauptgrund ist, warum Sie Multithreading im Auge behalten, kann PyPy die Geschwindigkeit möglicherweise ohne die Komplikationen von Multithreading bereitstellen.

Schließlich wurde die GIL selbst in Python 3 mit einem besseren Thread-Switching-Handler etwas überarbeitet. Alle zugrunde liegenden Annahmen - und Einschränkungen - bleiben jedoch bestehen. Es gibt immer noch eine GIL, und sie hält immer noch das Verfahren auf.

Kein GIL? Kein Problem

Trotz alledem geht die Suche nach einem Python ohne GIL, das mit vorhandenen Anwendungen kompatibel ist, weiter. Andere Implementierungen von Python haben die GIL vollständig abgeschafft, jedoch zu einem Preis. Jython wird beispielsweise auf der JVM ausgeführt und verwendet anstelle der GIL das Objektverfolgungssystem der JVM. IronPython verfolgt den gleichen Ansatz über die CLR von Microsoft. Beide leiden jedoch unter einer inkonsistenten Leistung und laufen manchmal viel langsamer als CPython. Sie können auch nicht ohne weiteres mit externem C-Code verbunden werden, sodass viele vorhandene Python-Anwendungen nicht funktionieren.

PyParallel, ein Projekt von Trent Nelson von Continuum Analytics, ist eine "experimentelle Proof-of-Concept-Gabel von Python 3, mit der mehrere CPU-Kerne optimal genutzt werden können". Die GIL wird nicht entfernt, aber ihre Wirkung wird durch Ersetzen des asyncModuls verbessert , sodass Apps, die  asyncfür Parallelität verwendet werden (z. B. Multithread-E / A wie ein Webserver), am meisten davon profitieren. Das Projekt ist seit mehreren Monaten inaktiv, aber in der Dokumentation heißt es, dass die Entwickler sich gerne Zeit nehmen, um es richtig zu machen, sodass es schließlich in CPython aufgenommen werden kann: "Es ist nichts Falsches daran, langsam und stetig zu sein, solange Sie unterwegs sind in die richtige Richtung."

Ein langjähriges Projekt der Entwickler von PyPy war eine Version von Python, die eine Technik namens "Software Transactional Memory" (PyPy-STM) verwendet. Laut den Entwicklern von PyPy besteht der Vorteil darin, dass "Sie kleinere Änderungen an Ihren vorhandenen, nicht mehrfach gelesenen Programmen vornehmen und sie dazu bringen können, mehrere Kerne zu verwenden".

PyPy-STM klingt nach Magie, hat aber zwei Nachteile. Erstens ist es eine laufende Arbeit, die derzeit nur Python 2.x unterstützt, und zweitens ist es immer noch ein Leistungseinbruch für Anwendungen, die auf einem einzelnen Kern ausgeführt werden. Da eine der Bestimmungen, die der Python-Entwickler Guido van Rossum für alle Versuche, die GIL aus CPython zu entfernen, zitiert, darin besteht, dass sein Ersatz die Leistung für Single-Core-Anwendungen mit einem Thread nicht beeinträchtigen sollte, wird eine solche Korrektur nicht in CPython landen in seinem aktuellen Zustand.

Beeil dich und warte

Larry Hastings, ein Python-Kernentwickler, teilte auf der PyCon 2016 einige seiner Ansichten darüber mit, wie die GIL entfernt werden könnte. Hastings dokumentierte seine Versuche, die GIL zu entfernen, und endete damit mit einer Version von Python, die keine GIL hatte, aber wegen ständiger Cache-Fehler quälend langsam lief.

Sie können die GIL verlieren, fasste Hastings zusammen, aber Sie müssen eine Möglichkeit haben, um sicherzustellen, dass jeweils nur ein Thread globale Objekte ändert - beispielsweise indem ein dedizierter Thread im Interpreter solche Statusänderungen verarbeitet.

Eine langfristig gute Nachricht ist, dass Entwickler, die die Sprache verwenden, bereits darauf vorbereitet sind, Multithreading zu nutzen, wenn CPython die GIL ablegt. Viele Änderungen, die jetzt in die Python-Syntax integriert sind, wie Warteschlangen und die Schlüsselwörter async/ awaitfür Python 3.5, erleichtern die Aufteilung von Aufgaben auf Kerne auf hoher Ebene.

Der Arbeitsaufwand, der erforderlich ist, um Python GIL-frei zu machen, garantiert jedoch, dass es zuerst in einer separaten Implementierung wie PyPy-STM angezeigt wird. Diejenigen, die ein System ohne GIL ausprobieren möchten, können dies durch eine solche Anstrengung eines Drittanbieters tun, aber das ursprüngliche CPython wird wahrscheinlich vorerst unberührt bleiben. Wir hoffen, dass das Warten nicht mehr lange dauert.