Wczesna próba usunięcia Pythona GIL spowodowała złą wydajność: Dlaczego?

13

W tym poście od twórcy Pythona, Guido Van Rossuma, wspomniano o wczesnej próbie usunięcia GIL z Pythona:

Zostało to już wcześniej wypróbowane, z rozczarowującymi rezultatami, dlatego sam niechętnie wkładam w to wiele wysiłku. W 1999 roku Greg Stein (wraz z Markiem Hammondem?) Opracował rozwidlenie Pythona (1.5), które usunęło GIL, zastępując go drobnoziarnistymi blokadami wszystkich zmiennych danych. Przekazał także łatki, które usunęły wiele zależności od globalnych zmiennych danych, które zaakceptowałem. Jednak po przeprowadzeniu testów porównawczych wykazano, że nawet na platformie z najszybszym programem prymitywnym blokującym (w tym czasie systemem Windows) spowolniło wykonywanie wątków jednowątkowych prawie dwukrotnie, co oznacza, że ​​na dwóch procesorach można uzyskać trochę więcej pracy wykonane bez GIL niż na jednym procesorze z GIL. To nie wystarczyło, a łatka Grega zniknęła w zapomnieniu. (Zobacz opis Grega na temat wydajności.)

Trudno mi się kłócić z rzeczywistymi wynikami, ale naprawdę zastanawiam się, dlaczego tak się stało. Przypuszczalnie głównym powodem, dla którego usunięcie GIL z CPython jest tak trudne, jest system zarządzania pamięcią zliczania referencji. Typowym programem Python zadzwoni Py_INCREFi Py_DECREFtysiące lub miliony razy, dzięki czemu jest kluczowy punkt rywalizacji gdybyśmy zamki owinąć wokół niego.

Ale nie rozumiem, dlaczego dodanie prymitywów atomowych będzie spowalniać jednego programu gwintowaną. Załóżmy, że właśnie zmodyfikowaliśmy CPython, tak aby zmienna przeliczania w każdym obiekcie Pythona była atomowa. A następnie wykonujemy przyrost atomowy (instrukcja pobierania i dodawania), gdy musimy zwiększyć liczbę referencji. To sprawiłoby, że liczenie odniesień w Pythonie byłoby bezpieczne i nie powinno mieć żadnego ograniczenia wydajności dla aplikacji jednowątkowej, ponieważ nie byłoby rywalizacji o blokowanie.

Ale, niestety, wiele osób mądrzejszych ode mnie próbowało i poniosło porażkę, więc oczywiście coś tu brakuje. Co jest złego w sposobie, w jaki patrzę na ten problem?

Siler
źródło
1
Pamiętaj, że operacja przeliczania nie byłaby jedynym miejscem wymagającym synchronizacji. Cytat wspomina o „drobnoziarnistych blokadach wszystkich modyfikowalnych struktur danych”, które, jak zakładam, zawierają przynajmniej muteks dla każdej listy i obiektu słownika. Ponadto nie sądzę, aby operacje na liczbach całkowitych atomowych były tak skuteczne, jak ich nieatomowy odpowiednik, niezależnie od sprzeczności, czy masz na to źródło?
po prostu dlatego, że operacje atomowe przebiegają wolniej niż nieatomowe odpowiedniki. To, że jest to pojedyncza instrukcja, nie oznacza, że ​​jest to banalne pod maską. Zobacz to do dyskusji
Móż

Odpowiedzi:

9

Nie jestem zaznajomiony z widelcem Grega Stein Pythona, więc jeśli chcesz, pomiń to porównanie jako spekulatywną analogię historyczną. Ale to było dokładnie historyczne doświadczenie wielu baz kodowych infrastruktury przechodzących od jedno- do wielowątkowych implementacji.

Zasadniczo każda implementacja Unixa, którą badałem w latach 90. - AIX, DEC OSF / 1, DG / UX, DYNIX, HP-UX, IRIX, Solaris, SVR4 i SVR4 MP - wszystko przeszło dokładnie przez tego rodzaju „ delikatniejsze ryglowanie - teraz jest wolniejsze !! " problem. DBMS, które śledziłem - DB2, Ingres, Informix, Oracle i Sybase - wszystkie też przez to przeszły.

Słyszałem, że „te zmiany nie spowolnią nas, gdy uruchamiamy jednowątkowy” milion razy. To nigdy tak nie działa. Prosty czyn warunkowego sprawdzenia „czy działamy wielowątkowo, czy nie?” dodaje prawdziwy narzut, szczególnie w przypadku wysoce wydajnych procesorów. Operacje atomowe i sporadyczne blokady blokowania dodane w celu zapewnienia integralności współdzielonych struktur danych muszą być wywoływane dość często i są one bardzo powolne. Prymitywy blokowania / synchronizacji pierwszej generacji również były powolne. Większość zespołów wdrożeniowych ostatecznie dodaje kilka klas prymitywów o różnych „mocach”, w zależności od tego, ile ochrony blokad było potrzebne w różnych miejscach. Potem zdają sobie sprawę, że początkowo uderzali prymitywami blokującymi nie było tak naprawdę właściwym miejscem, więc musieli się profilować, projektować wokół znalezionych wąskich gardeł, i systematycznie roto-till. Niektóre z tych punktów krytycznych ostatecznie uzyskały przyspieszenie systemu operacyjnego lub sprzętu, ale cała ewolucja zajęła 3-5 lat, absolutnie minimum. Tymczasem wersje MP lub MT utykały pod względem wydajności.

W przeciwnym razie wyrafinowane zespoły programistów argumentowały, że takie spowolnienia są zasadniczo trwałym i trudnym faktem życia. IBM np. Odmówił włączenia AMP przez SMP przez co najmniej 5 lat po konkursie, nieugięty, że jednowątkowy był po prostu lepszy. Sybase użył tych samych argumentów. Jedynym powodem, dla którego w końcu pojawiły się niektóre zespoły, było to, że wydajności pojedynczego wątku nie można już w rozsądny sposób poprawić na poziomie procesora. Zostali zmuszeni albo przejść do MP / MT, albo zaakceptować posiadanie coraz bardziej niekonkurencyjnego produktu.

Aktywna współbieżność jest trudna. I to jest zwodnicze. Wszyscy rzucają się na to, myśląc „to nie będzie takie złe”. Potem uderzają w ruchomy piasek i muszą przedrzeć się. Widziałem, jak to się dzieje z co najmniej tuzinem dobrze finansowanych, inteligentnych zespołów z marką. Zasadniczo wydawało się, że minęło co najmniej pięć lat po wyborze wielowątkowego „powrotu do miejsca, w którym powinny być, pod względem wydajności” z produktami MP / MT; większość nadal znacząco poprawiała wydajność / skalowalność MP / MT nawet dziesięć lat po dokonaniu zmiany.

Więc spekuluję, że bez poparcia i wsparcia ze strony GvR, nikt nie podjął długiego wysiłku dla Pythona i jego GIL. Nawet gdyby mieli to zrobić dzisiaj, byłby czas w Pythonie 4.x, zanim powiesz „Wow! Jesteśmy naprawdę ponad garbem MT!”

Być może istnieje jakaś magia, która oddziela Pythona i jego środowisko wykonawcze od wszystkich innych programów infrastruktury stanowej - wszystkich wcześniejszych wersji językowych, systemów operacyjnych, monitorów transakcji i menedżerów baz danych. Ale jeśli tak, to jest wyjątkowa lub prawie taka. Wszyscy inni, którzy usunęli odpowiednik GIL, potrzebowali ponad pięciu lat ciężkiego, zaangażowanego wysiłku i inwestycji, aby przejść od MT-not do MT-hot.

Jonathan Eunice
źródło
2
+1 Tyle czasu zajęło stworzenie wielowątkowego Tcl z dość małym zespołem programistów. Kod był wcześniej bezpieczny dla MT, ale miał nieprzyjemne problemy z wydajnością, głównie w zarządzaniu pamięcią (co, jak podejrzewam, jest bardzo gorącym obszarem dla dynamicznych języków). Doświadczenie to jednak tak naprawdę nie przenosi się na Pythona inaczej niż w najbardziej ogólnych terminach; dwa języki mają zupełnie różne modele wątków. Po prostu… spodziewaj się hasła i oczekuj dziwnych błędów…
Donal Fellows
-1

Kolejna szalona hipoteza: w 1999 r. Linux i inne Uniksy nie miały tak wydajnej synchronizacji jak teraz futex(2)( http://en.wikipedia.org/wiki/Futex ). Te pojawiły się około 2002 r. (I zostały połączone w 2.6 w 2004 r.).

Ponieważ wszystkie wbudowane struktury danych muszą być zsynchronizowane, blokowanie kosztuje dużo. Ӎσᶎ już wskazał, że operacje atomowe nie są konieczne tanie.

Sahib
źródło
1
Czy masz coś na poparcie tego? czy to prawie spekulacja?
1
Cytat GvR opisuje wydajność „na platformie z najszybszym prymitywem blokującym (w tym czasie Windows)”, więc powolne blokady w Linuksie nie są istotne.