PHP i JIT
Trzeba przyznać, że PHP rozwija się dosyć wolno. Główne wydania pojawią się średnio co roku, jednak ilość zmian nie jest jakaś przytłaczająca, a nadal brakuje dosyć istotnych elementów jak np. klas generycznych. Jednak jeśli chodzi o wzrost wydajności to wygląda to na prawdę bardzo dobrze, co wydanie to jest coś na plus. Dlatego chcąc nie chcąc nadal uważam ten język za użyteczny, a jeśli miałbym się na coś przesiadać to pewnie najsensowniejszym wyborem byłaby java, w której to znowu nie mógłbym znieść rozwleczonej składni. Przechodząc jednak do rzeczy, to niedawno pojawiła się wersja PHP 7.3, zapowiedziana została również wersja 7.4, w której mają pojawić się typowane właściwości. Dodatkowo dalszy rozwój zmierza w kierunku PHP 8.0, przy którym to wydaniu pojawiło się pojęcie JIT. Dlatego też postanowiłem trochę lepiej się tej kwestii przyjrzeć.
Jak obecnie działa interpreter PHP?
PHP jest językiem w teorii interpretowanym (niżej można znaleźć pewne wyjaśnienie tej kwestii), to znaczy, że nie jest bezpośrednio kompilowany do języka maszynowego. Jednak proces przetwarzania kodu wcale nie jest tak prosty jak mogłoby się wydawać.
Lexing
Pierwszym krokiem jest tzw. analiza leksykalna (ang. lexing), w którym to kod grupowany jest w poszczególne wyrażenia, na przykład:
- tag otwarcia <?php
- zmienna $x
- znak spacji
- symbol =
- znak spacji
- wartość numeryczna 1
- średnik ;
Co odpowiada kodowi jak poniżej:
<?php $x = 1;
Parsing
W następnej kolejności wykonywane jest parsowanie wyniku z poprzedniego kroku. Rezultatem tego etapu jest wygenerowane AST (ang. Abstract Syntax Tree), czyli po prostu reprezentacja kodu za pomocą drzewa, która dla kodu podanego wyżej wygląda mniej więcej tak.
ast\Node Object ( [kind] => 132 [flags] => 0 [lineno] => 1 [children] => Array ( [0] => ast\Node Object ( [kind] => 517 [flags] => 0 [lineno] => 2 [children] => Array ( [var] => ast\Node Object ( [kind] => 256 [flags] => 0 [lineno] => 2 [children] => Array ( [name] => x ) ) [expr] => 1 ) ) ) )
Compilation
Kolejnym krokiem jest kompilacja wygenerowanego AST. Rezultatem tej operacji są tzw. opcodes. Nie jest to kod maszynowy, jednak postać, którą w dużo łatwiejszy sposób niż oryginalny kod PHP Zend Virtual Machine zamienia na kod maszynowy. Na tym etapie wykonywane są również pewne optymalizacje jak np. wpisywanie wartości wyrażeń, które da się obliczyć, bo opierają się na stałych. Gdy dla przykładu zamiast magicznej liczby 3600 odpowiadającej liczbie sekund w godzinie napiszemy 60*60, to na etapie kompilacji w opcodes znajdzie się wartość 3600. Dla nas jako programistów czytelniej wygląda 60*60, dla wydajności w tym wypadku jest to bez znaczenia.
Opcodes generowane są przy pierwszym uruchomieniu, a następnie zapisywane w cache, tak aby kolejne wywołania nie musiały przechodzić od nowa wszystkich powyższych kroków.
Preloading
Powyższe kroki interpretera PHP pozwalają na w miarę wydajne wykonywanie kodu PHP. Piszę w miarę, bo jednak pewne rzeczy można ulepszyć. Takie ulepszenie prawdopodobnie pojawi się już w wersji 7.4. Tutaj można znaleźć RFC: https://wiki.php.net/rfc/preload. Generalnie sposób działania interpretera wymaga i tak sprawdzania plików zapisanych w cache czy nie zostały one zmodyfikowane, jednak zaproponowana funkcja pozwoli wrzucić niektóre pliki do cache w taki sposób, aby interpreter nie interesował się ich zmianami.
Czym jest JIT?
JIT oznacza w tym wypadku Just in time compilation. W przypadku zwykłej kompilacji proces wygląda w taki sposób, że po napisaniu programu np. w C++ kompilujemy go w całości do postaci binarnej. JIT polega na kompilacji kodu przed jego wykonaniem. Wygląda to w taki sposób:
- kod kompilowany jest do kodu pośredniego (opcodes)
- maszyna wirtualna kompiluje kod pośredni do kodu maszynowego
Może się tutaj nasunąć pytanie po co w ogóle stosować podejście JIT, skoro moglibyśmy stosować kompilację znaną chociażby z C++. Jednak w przypadku tej kompilacji problem stanowią różne architektury systemów, przygotowanie wersji na każdą architekturę z osobna jest dosyć problematyczne. Jednak coś za coś, języki kompilowane w ten sposób mają zwykle większą wydajność.
Jednak JIT to swego rodzaju kompromis pomiędzy kompilacją, a interpretacją kodu. Zyskujemy większą wydajność niż przy samym interpretowaniu co każde wykonanie, a pozbywamy się największej wady dotyczącej problemów z różnymi architekturami systemów.
JIT i PHP
Z opisu działania procesu interpretera PHP wynika, że jest już używane rozwiązanie podobne do JIT. Obecnie jednak do cache zapisywane są opcodes, czyli kod pośredni. Za każdym razem jednak wykonywana jest kompilacja z kodu pośredniego do kodu maszynowego. Natomiast w przypadku JIT do cache miałby trafiać kod maszynowy, co pozwoliłoby na wzrost wydajności. Kolejne poprawienie wydajności może spowodować, że PHP znajdzie zastosowania w miejscach gdzie zwykle nie było brane nawet pod uwagę.
JIT może również spowodować szybszy rozwój samego języka PHP. Obecnie do pisania rozszerzeń trzeba mieć ogromną wiedzę na temat niskopoziomowego programowania w C. W przypadku wdrożenia JIT rozszerzenia będzie można pisać po prostu w PHP, bez znacznego spadku wydajności w stosunku do rozwiązań opartych na C.
Czy PHP jest faktycznie językiem interpretowanym?
Obecnie mówienie, że PHP jest językiem interpretowanym jest pewnym nadużyciem. Chyba, że Javę określimy również jako język interpretowany. Różni się w zasadzie tylko sposób kompilacji, w Javie kompilacja odbywa się przed wykonaniem do kodu bajtowego, a w PHP kod kompilowany jest przy pierwszym wykonaniu do opcodes. Następnie w Javie kod bajtowy kompilowany jest przez JVM (Java Virtual Machine) do kodu maszynowego, a w przypadku PHP opcodes kompilowane jest do kodu maszynowego. Więc różni się tylko moment wygenerowania kodu pośredniego: kodu bajtowego w Javie i opcodes w PHP. Ewentualnie, gdy w PHP wyłączymy zapisywanie opcodes do cache i kompilacja do opcodes będzie przy każdym wykonaniu kodu to wtedy mamy do czynienia z językiem interpretowanym. Tylko wyłączanie cachowania opcodes to znaczna utrata wydajności.
Podsumowanie
Jeśli faktycznie w PHP 8.0 zostanie wprowadzony JIT, co przełoży się na wzrost wydajności i możliwość zastosowania języka też w innych miejscach niż web to powinno to raczej zamknąć wszelkie dyskusje na temat bliskiego końca PHP, co niektórzy zwiastują od kilku lat. Rozwój języka, może nie jest zbyt szybki, ale myślę, że najważniejsze jest to, że idzie w dobrym kierunku. Oczywiście nadal brakuje sporo elementów jak np. klas generycznych, które bardzo by się przydały. Narzekać również można na pewien brak konsekwencji w niektórych konstrukcjach jak np. w funkcjach array_filter i array_map, gdzie parametry powinny być raczej w tej samej kolejności, a obie z tych funkcji przyjmują je w innych kolejnościach. W każdym razie liczę, że doczekamy się również jakichś zmian na lepsze również w tych aspektach.
Subscribe and master unit testing with my FREE eBook (+60 pages)! 🚀
In these times, the benefits of writing unit tests are huge. I think that most of the recently started projects contain unit tests. In enterprise applications with a lot of business logic, unit tests are the most important tests, because they are fast and can us instantly assure that our implementation is correct. However, I often see a problem with good tests in projects, though these tests’ benefits are only huge when you have good unit tests. So in this ebook, I share many tips on what to do to write good unit tests.
3 komentarze