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)
źródło
Odpowiedzi:
Ż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”.
źródło
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
eval
funkcję, co oznacza, że kompilator JIT nie może przyjmować założeń o niedziałaniu, nawet jeśli wykonuje analizę całego programu, o ileeval
jest 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 funkcjieval
. 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.źródło
eval
uruchamiając ponowną kompilację i / lub dezoptymalizację.