Der frühe Versuch, Python GIL zu entfernen, führte zu einer schlechten Leistung: Warum?

13

Dieser Beitrag des Python-Erstellers Guido Van Rossum erwähnt einen frühen Versuch, die GIL aus Python zu entfernen:

Dies wurde bereits zuvor mit enttäuschenden Ergebnissen versucht, weshalb ich mich nur ungern selbst anstrengen möchte. 1999 produzierte Greg Stein (mit Mark Hammond?) Eine Python-Gabel (1,5, glaube ich), die die GIL entfernte und durch fein abgestimmte Sperren für alle veränderlichen Datenstrukturen ersetzte. Er reichte auch Patches ein, die viele der Abhängigkeiten von globalen veränderlichen Datenstrukturen beseitigten, die ich akzeptierte. Nach dem Benchmarking zeigte sich jedoch, dass selbst auf der Plattform mit dem schnellsten Locking-Primitiv (zu der Zeit Windows) die Single-Thread-Ausführung fast doppelt so stark verlangsamt wurde, was bedeutet, dass auf zwei CPUs nur ein wenig mehr Arbeit anfällt auf die GIL verzichtet als auf eine einzelne CPU mit der GIL. Das war nicht genug und Gregs Fleck verschwand in Vergessenheit. (Siehe Gregs Bericht über die Aufführung.)

Ich kann kaum mit den tatsächlichen Ergebnissen streiten, aber ich frage mich wirklich, warum das passiert ist. Vermutlich liegt der Hauptgrund dafür, dass das Entfernen der GIL aus CPython so schwierig ist, in dem Speicherverwaltungssystem mit Referenzzählung. Ein typisches Python - Programm aufrufen Py_INCREFund Py_DECREFTausende oder Millionen Mal, eine Schlüsselverzweigungspunkt zu machen , wenn wir um ihn herum wickeln Schlösser waren.

Aber ich verstehe nicht, warum das Hinzufügen von atomaren Primitiven ein einzelnes Thread-Programm verlangsamen würde . Angenommen, wir haben gerade CPython so geändert, dass die refcount-Variable in jedem Python-Objekt ein atomares Grundelement war. Und dann machen wir einfach eine atomare Inkrementierung (Fetch-and-Add-Anweisung), wenn wir den Referenzzähler erhöhen müssen. Dies würde die Python-Referenzzählung thread-sicher machen und sollte keine Leistungseinbußen bei einer Single-Thread-Anwendung zur Folge haben, da es keine Sperrkonflikte geben würde.

Aber leider haben viele Leute, die schlauer als ich sind, es versucht und sind gescheitert, also vermisse ich hier offensichtlich etwas. Was ist falsch daran, wie ich dieses Problem betrachte?

Siler
quelle
1
Beachten Sie, dass der Refcount-Vorgang nicht der einzige Ort ist, an dem eine Synchronisierung erforderlich ist. Das Zitat erwähnt "feinkörnige Sperren für alle veränderlichen Datenstrukturen", von denen ich annehme, dass es mindestens einen Mutex für jedes Listen- und Wörterbuchobjekt gibt. Ich denke auch, dass atomare Ganzzahloperationen nicht so effizient sind wie das nicht-atomare Äquivalent, unabhängig von der Konkurrenz. Haben Sie eine Quelle dafür?
einfach, weil atomare Operationen langsamer sind als nichtatomare Äquivalente. Nur weil es eine einzelne Anweisung ist, heißt das nicht, dass sie unter der Haube trivial ist. Siehe dies für eine Diskussion
Móż

Antworten:

9

Ich bin mit der Greg Stein Python-Gabel nicht vertraut, also schließen Sie diesen Vergleich als spekulative historische Analogie aus, wenn Sie es wünschen. Dies war jedoch genau die historische Erfahrung vieler Infrastruktur-Codebasen, die von Single-Thread- zu Multi-Thread-Implementierungen übergingen.

Im Wesentlichen hat jede Unix-Implementierung, die ich in den 1990er Jahren studiert habe - AIX, DEC OSF / 1, DG / UX, DYNIX, HP-UX, IRIX, Solaris, SVR4 und SVR4 MP - genau diese Art von "Eingaben" durchlaufen feinere Verriegelung - jetzt ist es langsamer !! " Problem. Die DBMSs, denen ich gefolgt bin - DB2, Ingres, Informix, Oracle und Sybase -, haben sie alle ebenfalls durchlaufen.

Ich habe gehört, "diese Änderungen werden uns nicht bremsen, wenn wir Singlethreading ausführen", millionenfach. So funktioniert es nie. Der einfache Vorgang der bedingten Überprüfung, ob Multithreading ausgeführt wird oder nicht. Fügt echten Overhead hinzu, insbesondere bei CPUs mit hoher Auslastung. Atomare Operationen und gelegentliche Spin-Locks, die hinzugefügt wurden, um die Integrität gemeinsam genutzter Datenstrukturen zu gewährleisten, müssen häufig aufgerufen werden und sind sehr langsam. Die Sperr- / Synchronisationsprimitive der ersten Generation waren ebenfalls langsam. Die meisten Implementierungsteams fügen schließlich mehrere Klassen von Grundelementen in verschiedenen "Stärken" hinzu, je nachdem, wie viel Verriegelungsschutz an verschiedenen Stellen benötigt wurde. Dann stellten sie fest, dass der Ort, an dem sie anfänglich die Verriegelungsprimitive verriegelt hatten, nicht wirklich der richtige Ort war. und systematisch rotieren. Einige dieser Knackpunkte bekamen irgendwann eine Betriebssystem- oder Hardwarebeschleunigung, aber diese ganze Entwicklung dauerte mindestens 3-5 Jahre. In der Zwischenzeit humpelten die MP- oder MT-Versionen in Bezug auf die Leistung.

Ansonsten hoch entwickelte Entwicklungsteams haben argumentiert, dass solche Verlangsamungen im Grunde genommen eine hartnäckige und unlösbare Tatsache im Leben sind. IBM weigerte sich beispielsweise, AIX für mindestens 5 Jahre nach dem Wettbewerb für SMP zu aktivieren, und bestand darauf, dass Single-Threading einfach besser war. Sybase verwendete einige der gleichen Argumente. Der einzige Grund, warum einige der Teams letztendlich dazu kamen, war, dass die Single-Thread-Leistung auf CPU-Ebene nicht mehr angemessen verbessert werden konnte. Sie waren gezwungen, entweder MP / MT zu wählen oder ein zunehmend nicht wettbewerbsfähiges Produkt zu akzeptieren.

Die aktive Parallelität ist HARD. Und es täuscht. Alle stürzen sich darauf und denken: "Das wird nicht so schlimm." Dann schlagen sie auf den Treibsand und müssen hindurch. Ich habe dies mit mindestens einem Dutzend gut finanzierter, intelligenter Marken-Teams erlebt. Im Allgemeinen schien es mindestens fünf Jahre zu dauern, nachdem man sich für Multi-Thread entschieden hatte, um mit MP / MT-Produkten "wieder dahin zurückzukehren, wo sie sein sollten, was die Leistung anbelangt". Die meisten verbesserten die MP / MT-Effizienz / Skalierbarkeit auch zehn Jahre nach dem Wechsel noch erheblich.

Meine Spekulation ist also, dass, ohne die Zustimmung und Unterstützung von GvR, niemand den langen Weg für Python und seine GIL angetreten hat. Selbst wenn sie dies heute tun würden, wäre es Python 4.x, bevor Sie sagen würden: "Wow! Wir sind wirklich über den MT-Buckel hinaus!"

Vielleicht gibt es etwas Magisches, das Python und seine Laufzeit von allen anderen Stateful Infrastructure-Programmen trennt - allen Sprachlaufzeiten, Betriebssystemen, Transaktionsmonitoren und Datenbankmanagern, die es zuvor gab. Aber wenn ja, ist es einzigartig oder fast so. Alle anderen, die ein GIL-Äquivalent entfernen, haben mehr als fünf Jahre anstrengenden, engagierten Anstrengungen und Investitionen gebraucht, um von MT-not zu MT-hot zu gelangen.

Jonathan Eunice
quelle
2
+1 Das Multithreading von Tcl mit einem relativ kleinen Entwicklerteam dauerte ungefähr so ​​lange. Davor war der Code MT-sicher, hatte jedoch böse Leistungsprobleme, hauptsächlich bei der Speicherverwaltung (was meiner Meinung nach ein sehr heißer Bereich für dynamische Sprachen ist). Die Erfahrung überträgt sich jedoch nicht wirklich auf Python. Die beiden Sprachen haben völlig unterschiedliche Threading-Modelle. Nur ... erwarten Sie einen Slog und erwarten Sie seltsame Bugs ...
Donal Fellows
-1

Eine andere wilde Hypothese: 1999 hatten Linux und andere Unices keine so performante Synchronisation wie jetzt mit futex(2)( http://en.wikipedia.org/wiki/Futex ). Diese kamen um 2002 (und wurden um 2004 zu 2.6 zusammengeführt).

Da alle eingebauten Datenstrukturen synchronisiert werden müssen, kostet das Sperren viel. ӍσӍ hat bereits darauf hingewiesen, dass atomare Operationen nicht unbedingt billig sind.

Sahib
quelle
1
Haben Sie irgendetwas, um dies zu sichern? oder ist das fast spekulation?
1
Das GvR-Zitat beschreibt die Leistung "auf der Plattform mit dem schnellsten Sperrprimitiv (Windows zu der Zeit)", sodass langsame Sperren unter Linux nicht relevant sind.