Python zyskał reputację potężnego, elastycznego i łatwego w użyciu. Te zalety sprawiły, że jest on używany w ogromnej i wciąż rosnącej liczbie aplikacji, przepływów pracy i dziedzin. Jednak konstrukcja języka – jego interpretowana natura, dynamika uruchamiania – oznacza, że Python zawsze był o rząd wielkości wolniejszy niż języki maszynowe, takie jak C czy C++.
Na przestrzeni lat programiści wymyślili wiele sposobów na obejście ograniczeń szybkości Pythona. Na przykład, można pisać zadania wymagające dużej wydajności w C i opakowywać je w Pythona; wiele bibliotek uczenia maszynowego robi dokładnie to samo. Można też użyć Cythona, projektu, który pozwala na dodanie do kodu Pythona informacji o typie w czasie wykonywania, co pozwala na jego kompilację do języka C.
Ale obejścia nigdy nie są idealne. Czy nie byłoby wspaniale, gdybyśmy mogli po prostu wziąć istniejący program Pythona takim, jakim jest i uruchomić go znacznie szybciej? Właśnie na to pozwala PyPy.
PyPy vs. CPython
PyPy jest zamiennikiem dla standardowego interpretera Pythona, CPythona. Podczas gdy CPython kompiluje Pythona do pośredniego kodu bajtowego, który jest następnie interpretowany przez maszynę wirtualną, PyPy używa kompilacji just-in-time (JIT) do tłumaczenia kodu Pythona na natywny dla maszyny język asemblerowy.
Zależnie od wykonywanego zadania, zyski wydajności mogą być dramatyczne. Średnio, PyPy przyspiesza Pythona o około 7,6 razy, a niektóre zadania są przyspieszane 50 razy lub więcej. Interpreter CPython po prostu nie wykonuje tych samych rodzajów optymalizacji co PyPy i prawdopodobnie nigdy nie będzie, ponieważ nie jest to jeden z jego celów projektowych.
Najlepsze jest to, że niewiele do żadnego wysiłku jest wymagane ze strony programisty, aby odblokować zyski, które zapewnia PyPy. Po prostu zamień CPython na PyPy, i w większości przypadków gotowe. Istnieje kilka wyjątków, omówionych poniżej, ale deklarowanym celem PyPy jest uruchomienie istniejącego, niezmodyfikowanego kodu Pythona i zapewnienie mu automatycznego zwiększenia prędkości.
PyPy obsługuje obecnie zarówno Pythona 2, jak i Pythona 3, poprzez różne wcielenia projektu. Innymi słowy, musisz pobrać różne wersje PyPy, w zależności od wersji Pythona, którą będziesz używać. Gałąź Pythona 2 jest obecna znacznie dłużej, ale wersja Pythona 3 została przyspieszona w ostatnim czasie. Obecnie obsługuje zarówno Python 3.5 (jakość produkcyjna), jak i Python 3.6 (jakość beta).
Oprócz obsługi wszystkich podstawowych języków Pythona, PyPy współpracuje z większością narzędzi w ekosystemie Pythona, takich jak pip
do pakowania lub virtualenv
do środowisk wirtualnych. Większość pakietów Pythona, nawet tych z modułami C, powinna działać tak jak jest, choć istnieją ograniczenia, którymi zajmiemy się poniżej.
Jak działa PyPy
PyPy wykorzystuje techniki optymalizacji znalezione w innych kompilatorach just-in-time dla języków dynamicznych. Analizuje on działające programy Pythona, aby określić informacje o typie obiektów, gdy są one tworzone i używane w programach, a następnie wykorzystuje te informacje jako przewodnik do przyspieszenia działania. Na przykład, jeśli funkcja Pythona działa tylko z jednym lub dwoma różnymi typami obiektów, PyPy generuje kod maszynowy do obsługi tych konkretnych przypadków.
Optymalizacje PyPy są obsługiwane automatycznie podczas uruchamiania, więc generalnie nie trzeba dostosowywać jego wydajności. Zaawansowany użytkownik może eksperymentować z opcjami wiersza poleceń PyPy, aby wygenerować szybszy kod dla specjalnych przypadków, ale tylko rzadko jest to konieczne.
PyPy odbiega również od sposobu, w jaki CPython obsługuje niektóre wewnętrzne funkcje, ale stara się zachować kompatybilne zachowania. Na przykład, PyPy obsługuje zbieranie śmieci inaczej niż CPython. Nie wszystkie obiekty są natychmiastowo zbierane po wyjściu poza zakres, więc program Pythona uruchomiony pod PyPy może wykazywać większy ślad pamięciowy niż ten uruchomiony pod CPythonem. Ale nadal można korzystać z wysokopoziomowych mechanizmów kontroli odśmiecania Pythona, dostępnych w module gc
, takich jak gc.enable()
, gc.disable()
i gc.collect()
.
Jeśli chcesz uzyskać informacje o zachowaniu JIT Pythona w czasie wykonywania, PyPy zawiera moduł pypyjit
, który udostępnia wiele haków JIT dla aplikacji Pythona. Jeśli masz funkcję lub moduł, który wydaje się słabo radzić sobie z JIT, pypyjit
pozwala uzyskać szczegółowe statystyki na jego temat.
Inny moduł specyficzny dla PyPy, __pypy__
, eksponuje inne cechy specyficzne dla PyPy, więc może być przydatny do pisania aplikacji, które wykorzystują te cechy. Z powodu dynamiki Pythona, możliwe jest konstruowanie aplikacji Pythona, które używają tych funkcji, gdy PyPy jest obecny i ignorują je, gdy nie jest obecny.
Ograniczenia PyPy
Magiczne jak PyPy może się wydawać, nie jest magią. PyPy ma pewne ograniczenia, które zmniejszają lub eliminują jego skuteczność dla pewnych rodzajów programów. Niestety, PyPy nie jest całkowicie uniwersalnym zamiennikiem stockowego runtime’u CPython.
PyPy działa najlepiej z aplikacjami czysto pythonowymi
PyPy zawsze działał najlepiej z „czystymi” aplikacjami Pythona – tzn. aplikacjami napisanymi w Pythonie i niczym więcej. Pakiety Pythona, które mają interfejs z bibliotekami C, takie jak NumPy, nie radziły sobie tak dobrze z powodu sposobu, w jaki PyPy emuluje natywne interfejsy binarne CPythona.
Deweloperzy PyPy odeszli od tego problemu i sprawili, że PyPy jest bardziej kompatybilny z większością pakietów Pythona, które zależą od rozszerzeń C. Numpy, na przykład, działa teraz bardzo dobrze z PyPy. Ale jeśli chcesz uzyskać maksymalną kompatybilność z rozszerzeniami C, użyj CPython.
PyPy działa najlepiej z dłużej działającymi programami
Jednym z efektów ubocznych tego, jak PyPy optymalizuje programy Pythona, jest to, że dłużej działające programy najbardziej korzystają z jego optymalizacji. Im dłużej program działa, tym więcej informacji o typie w czasie działania może zebrać PyPy i tym więcej optymalizacji może wykonać. Jednorazowe skrypty Pythona nie skorzystają z tego typu rzeczy. Aplikacje, które z tego korzystają, zazwyczaj mają pętle, które działają przez długi czas lub działają bez przerwy w tle – na przykład frameworki internetowe.
PyPy nie wykonuje kompilacji w czasie rzeczywistym
PyPy kompiluje kod Pythona, ale nie jest kompilatorem kodu Pythona. Ze względu na sposób, w jaki PyPy wykonuje swoje optymalizacje i nieodłączny dynamizm Pythona, nie ma sposobu, aby wyemitować wynikowy kod JIT jako samodzielną binarkę i użyć go ponownie. Każdy program musi być kompilowany dla każdego uruchomienia. Jeśli chcesz skompilować Pythona do szybszego kodu, który może działać jako samodzielna aplikacja, użyj Cythona, Numby lub obecnie eksperymentalnego projektu Nuitka.