Jakie cechy semantyczne Pythona (i innych dynamicznych języków) przyczyniają się do jego spowolnienia?

26

Nie znam dobrze Python. Staram się lepiej zrozumieć, jakie dokładnie cechy dynamicznych języków (à la Python, Lua, Scheme, Perl, Ruby, ...) zmuszają ich implementacje do powolnego działania.

Przykładowo, maszynowość meta Lua 5.3 intuicyjnie spowolniłaby Lua dość powoli, ale w praktyce Lua jest podobno dość szybka (i szybsza niż Python).

Mam również intuicję (być może niewłaściwą), że ponieważ na obecnych procesorach pamięć jest znacznie wolniejsza niż surowe obliczenia (dostęp do pamięci z brakiem pamięci podręcznej potrzebuje tyle samo czasu, co setki operacji arytmetycznych), dynamiczne sprawdzanie typu (à la if (value->type != INTEGER_TAG) return;in Cl) może działać dość szybko.

Oczywiście, analiza całego programu (podobnie jak robi to implementacja Stalina Scheme ) może zrobić dynamiczną implementację języka, ponieważ tłumacz działa szybko, ale udawajmy, że na początku nie mam czasu na zaprojektowanie całego analizatora programu.

(W pewnym sensie projektuję język dynamiczny na monitorze MELT , a niektóre z nich zostaną przetłumaczone na C)

Basile Starynkevitch
źródło
1
Porady dotyczące wydajności Lua , które wyjaśniają, dlaczego niektóre programy Lua są wolne i jak je naprawić.
Robert Harvey,

Odpowiedzi:

24

Jakie cechy semantyczne Pythona (i innych dynamicznych języków) przyczyniają się do jego spowolnienia?

Żaden.

Wydajność implementacji językowych jest funkcją pieniędzy, zasobów i doktoratów, a nie cech językowych. Self jest znacznie bardziej dynamiczny niż Smalltalk i nieco bardziej dynamiczny niż Python, Ruby, ECMAScript lub Lua i miał maszynę wirtualną, która przewyższała wszystkie istniejące maszyny wirtualne Lisp i Smalltalk (w rzeczywistości dystrybucja Self dostarczana z małym interpreterem Smalltalk napisanym w języku Self , a nawet to było szybsze niż większość istniejących maszyn wirtualnych Smalltalk) i było konkurencyjne, a czasem nawet szybsze niż implementacje C ++ w tamtym czasie.

Następnie Sun przestał finansować Self, a IBM, Microsoft, Intel i Co. rozpoczęły finansowanie C ++, a trend się odwrócił. Auto-programiści opuścili Sun, aby założyć własną firmę, w której wykorzystali technologię opracowaną dla Self VM, aby zbudować jedną z najszybszych maszyn wirtualnych Smalltalk w historii (Animorphic VM), a następnie Sun odkupił tę firmę i nieco zmodyfikowaną wersję że Smalltalk VM jest teraz lepiej znany pod nazwą „HotSpot JVM”. Jak na ironię, programiści Java patrzą na dynamiczne języki za to, że są „powolni”, podczas gdy w rzeczywistości Javabyło powolne, dopóki nie przyjęła dynamicznej technologii językowej. (Tak, to prawda: HotSpot JVM jest zasadniczo maszyną wirtualną Smalltalk. Weryfikator kodu bajtowego sprawdza wiele typów, ale gdy kod bajtowy zostanie zaakceptowany przez weryfikator, maszyna wirtualna, a zwłaszcza optymalizator i JIT, tak naprawdę nie duże zainteresowanie typami statycznymi!)

CPython po prostu nie robi wielu rzeczy, które sprawiają, że dynamiczne języki (a raczej dynamiczna wysyłka) są szybkie: dynamiczna kompilacja (JIT), dynamiczna optymalizacja, inkluzje spekulacyjne, optymalizacja adaptacyjna, dynamiczna deoptymalizacja, dynamiczne sprzężenie zwrotne / wnioskowanie. Istnieje również problem polegający na tym, że prawie cała podstawowa i standardowa biblioteka jest napisana w C, co oznacza, że ​​nawet jeśli nagle przyspieszysz Python 100x, to ci to nie pomoże, ponieważ około 95% kodu wykonywanego przez Program w języku Python to C, a nie Python. Gdyby wszystko napisano w Pythonie, nawet umiarkowane przyspieszenia wywołałyby efekt lawinowy, w którym algorytmy stają się szybsze, a podstawowe struktury danych stają się szybsze, ale oczywiście podstawowe struktury danych są również używane w algorytmach, a podstawowe algorytmy i podstawowe dane struktury są używane wszędzie indziej,

Jest kilka rzeczy, które są bardzo złe dla zarządzanych przez pamięć języków OO (dynamicznych lub nie) w dzisiejszych systemach. Pamięć wirtualna i ochrona pamięci mogą być zabójcze w szczególności dla wydajności czyszczenia pamięci, a ogólnie wydajności systemu. I jest to całkowicie niepotrzebne w języku bezpiecznym dla pamięci: po co chronić się przed nielegalnymi dostępami do pamięci, skoro na początku nie ma dostępu do pamięci w języku? Azul doszedł do wniosku, że wykorzystuje nowoczesne potężne MMU (Intel Nehalem i nowsze oraz odpowiednik AMD), aby pomóc w usuwaniu śmieci zamiast utrudniania go, ale mimo że jest on obsługiwany przez procesor, obecne podsystemy pamięci głównych systemów operacyjnych nie są wystarczająco mocne aby na to pozwolić (dlatego właśnie JVM Azul'a faktycznie działa zwirtualizowany poza samym metalem system operacyjny, nie w nim).

W projekcie Singularity OS Microsoft zmierzył wpływ ~ 30% na wydajność systemu przy użyciu ochrony MMU zamiast systemu typu do separacji procesów.

Inną rzeczą, którą Azul zauważył, budując swoje wyspecjalizowane procesory Java, było to, że współczesne procesory głównego nurtu koncentrują się na całkowicie niewłaściwej rzeczy, próbując zmniejszyć koszty braków pamięci podręcznej: próbują zmniejszyć liczbę braków pamięci podręcznej poprzez takie rzeczy, jak przewidywanie gałęzi, pobieranie pamięci, i tak dalej. Ale w mocno polimorficznym programie OO wzorce dostępu są w zasadzie pseudolosowe, po prostu nie ma nic do przewidzenia. Tak więc wszystkie te tranzystory są po prostu zmarnowane, a tym, co należy zrobić, jest zmniejszenie kosztu każdej pojedynczej pamięci podręcznej. (Całkowity koszt to koszt #misses *, główny nurt próbuje obniżyć pierwszy, Azul drugi.) Java Compute Accelerators firmy Azul może mieć 20000 równoczesnych braków pamięci podręcznej w locie i nadal robić postępy.

Kiedy Azul zaczął, ale myślałem , że oni trochę off-the-shelf I / O komponenty i zaprojektować własną specjalistyczną rdzeń procesora, ale to, co rzeczywiście skończyło się na konieczności robić dokładnie odwrotnie: miały one raczej standardowy off-the- półka 3-adresowy rdzeń RISC i zaprojektował własny kontroler pamięci, MMU i podsystem pamięci podręcznej.

tl; dr : „powolność” Pythona nie jest własnością języka, ale a) jego naiwną (podstawową) implementacją oraz b) faktem, że nowoczesne procesory i systemy operacyjne są specjalnie zaprojektowane tak, aby C działał szybko, i funkcje mają dla C albo nie pomagają (pamięć podręczna), a nawet aktywnie szkodzą (pamięć wirtualna) wydajności Pythona.

Możesz tu wstawić praktycznie każdy język zarządzany pamięcią z dynamicznym polimorfizmem ad-hoc… jeśli chodzi o wyzwania związane z wydajną implementacją, nawet Python i Java są w zasadzie „tym samym językiem”.

Jörg W Mittag
źródło
Czy masz link lub odniesienie do Azul?
Basile Starynkevitch
4
Nie zgadzam się, że semantyka języka nie ma wpływu na jego zdolność do skutecznego wdrożenia. Tak, dobra realizacja JIT z pierwszej klasy optymalizacje i analizę można wykonać ogromną poprawę wydajności języka, ale w końcu istnieją pewne aspekty semantyki, która nieuchronnie skończyć się zatorów. Niezależnie od tego, czy jest to wymóg C dotyczący ścisłego aliasingu wskaźników, czy wymóg Pythona, aby operacje listy były wykonywane atomowo, istnieją pewne decyzje semantyczne, które nieuchronnie kończą się na wydajności niektórych aplikacji.
Jules
1
Na marginesie ... czy masz odniesienie do tej 30% poprawy Singularity? Byłem zwolennikiem systemów operacyjnych opartych na języku od wielu lat, ale nigdy wcześniej nie widziałem tej liczby i uważam, że jest dość zaskakująca (liczby, na które patrzyłem w przeszłości, były bliższe 10%) i zastanawiam się, co zrobili, aby uzyskać tak dużą poprawę ...
Jules
5
@MasonWheeler: Ponieważ są tam tylko kiepskie implementacje Pythona. Żaden implementator Python nie wydał nawet niewielkiej części pieniędzy, ludzi, badań i zasobów, które IBM, Sun, Oracle, Google i Co. wydały na J9, JRockit, HotSpot i Co. Wszystkie 5 implementacji Pythona prawdopodobnie nawet nie mieć siłę roboczą, którą Oracle wydaje tylko na śmietnik. IBM pracuje nad implementacją Pythona opartą na Eclipse OMR (komponentowa platforma VM open source wyodrębniona z J9), jestem gotów się założyć, że jego wydajność będzie w zakresie wielkości J9
Jörg W Mittag
2
Rekord C jest powolny w porównaniu z Fortranem do pracy numerycznej, ponieważ Fortran wymusza ścisłe aliasing, dzięki czemu optymalizator może być bardziej agresywny.
Michael Shopsin
8

Podczas gdy obecna implementacja Pythona (której brakuje wielu optymalizacji wykonywanych przez inne dynamiczne języki, np. Nowoczesne implementacje Javascript i, jak zauważyłeś, Lua) jest źródłem większości problemów, ma pewne problemy semantyczne, które by to sprawiły trudno implementacji konkurować z innymi językami, przynajmniej w niektórych dziedzinach. Niektóre z nich są szczególnie warte rozważenia:

  • Definicja języka wymaga, aby operacje na liście i słowniku były atomowe. Oznacza to, że o ile kompilator JIT nie jest w stanie udowodnić, że żadne odwołanie do obiektu listy nie uniknęło bieżącego wątku (analiza, która jest trudna w wielu przypadkach i niemożliwa w ogólnym przypadku), musi zapewnić szeregowanie dostępu do obiektu (np. przez blokowanie). Implementacja CPython rozwiązuje ten problem, stosując znaną „globalną blokadę interpretera”, która uniemożliwia efektywne wykorzystanie kodu Pythona w środowiskach wieloprocesowych z technikami wielowątkowymi, a chociaż możliwe są inne rozwiązania, wszystkie mają problemy z wydajnością.

  • Python nie ma mechanizmu określającego użycie obiektów wartości; wszystko jest obsługiwane przez odniesienie, dodając dodatkową pośrednią tam, gdzie nie jest to konieczne. Chociaż w niektórych przypadkach kompilator JIT może wnioskować o obiektach wartości i automatycznie go optymalizować, nie można tego robić ogólnie i dlatego kod, który nie jest starannie napisany, aby zapewnić optymalizację jest możliwy (co jest poniekąd czarną sztuką) będzie cierpieć.

  • Python ma evalfunkcję, co oznacza, że ​​kompilator JIT nie może przyjmować założeń o niedziałaniu, nawet jeśli wykonuje analizę całego programu, o ile evaljest on użyty raz. Na przykład kompilator w języku Python nie może zakładać, że klasa nie ma podklas, a zatem dewializuje wywołania metod, ponieważ założenie to można później zanegować poprzez wywołanie funkcji eval. Zamiast tego musi przeprowadzić dynamiczne sprawdzanie typu, aby upewnić się, że założenia wykonane przez kod skompilowany w natywny sposób nie zostały unieważnione przed wykonaniem tego kodu.

Jules
źródło
3
Ten ostatni punkt można złagodzić, evaluruchamiając ponowną kompilację i / lub dezoptymalizację.
Jörg W Mittag
4
Nawiasem mówiąc, nie jest to również unikalne dla Pythona. Java (a raczej JVM) ma dynamiczne ładowanie kodu i dynamiczne łączenie, więc Analiza Hierarchii Klas jest odpowiednikiem rozwiązania tam również problemu zatrzymania. Jednak HotSpot szczęśliwie spekulacyjnie wprowadza metody polimorficzne, a jeśli coś w hierarchii klas ulegnie zmianie, cóż, po prostu usunie je z powrotem.
Jörg W Mittag