PHP 8.0: Nowe funkcje, klasy i JIT (4/4)
PHP w wersji 8.0 zostało wydane. Jest ona pełna nowych funkcji, jak żadna inna wersja wcześniej. Ich wprowadzenie zasłużyło na cztery osobne artykuły. W ostatniej części przyjrzymy się nowym funkcjom i klasom oraz przedstawimy kompilator Just in Time.
Nowe funkcje
Standardowa biblioteka PHP zawiera setki funkcji, a w wersji 8.0 pojawiło się sześć nowych. Nie wydaje się to dużo, ale większość z nich naprawia słabe punkty języka. Jest to zgodne z ideą wersji 8.0, która zacieśnia i konsoliduje PHP jak żadna inna wersja. Przegląd wszystkich nowych funkcji i metod można znaleźć w przewodniku po migracji.
str_contains()
str_starts_with()
str_ends_with()
Funkcje pozwalające określić, czy łańcuch zaczyna się, kończy lub zawiera podłańcuch.
if (str_contains('Nette', 'te')) {
...
}
Wraz z pojawieniem się tej trójcy, PHP definiuje sposób obsługi pustego łańcucha podczas wyszukiwania, do czego stosują się wszystkie inne powiązane funkcje, a mianowicie pusty łańcuch znajduje się wszędzie:.
str_contains('Nette', '') // true
str_starts_with('Nette', '') // true
strpos('Nette', '') // 0 (previously false)
Dzięki temu zachowanie trójcy jest całkowicie identyczne z analogami Nette:
str_contains() # Nette\Utils\String::contains()
str_starts_with() # Nette\Utils\String::startsWith()
str_ends_with() # Nette\Utils\String::endsWith()
Dlaczego te funkcje są tak ważne? Standardowe biblioteki wszystkich
języków są zawsze obciążone historycznym rozwojem; nie da się uniknąć
niespójności i błędów. Ale jednocześnie jest to świadectwo danego
języka. O dziwo, w 25-letnim PHP brakuje funkcji do tak podstawowych operacji,
jak zwracanie pierwszego lub ostatniego elementu tablicy, ucieczka przed HTML
bez przykrych niespodzianek (htmlspecialchars
nie ucieka przed
apostrofem), czy po prostu szukanie ciągu w ciągu. Nie trzyma się tego, że
można go w jakiś sposób ominąć, ponieważ wynikiem jest
nieczytelny i zrozumiały kod. To jest lekcja dla wszystkich autorów API.
Kiedy widzisz, że znaczną część dokumentacji funkcji zajmują wyjaśnienia
pułapek (takich jak wartości zwracane przez strpos
), to jest to
wyraźny znak, aby zmodyfikować bibliotekę i dodać
str_contains
.
get_debug_type()
Zastępuje przestarzały już get_type()
. Zamiast długich
typów jak integer
, zwraca obecnie używany int
, w
przypadku obiektów zwraca bezpośrednio typ:
Value | gettype() | get_debug_type() |
---|---|---|
'abc' |
string |
string |
[1, 2] |
array |
array |
231 |
integer |
int |
3.14 |
double |
float |
true |
boolean |
bool |
null |
NULL |
null |
new stdClass |
object |
stdClass |
new Foo\Bar |
object |
Foo\Bar |
function() {} |
object |
Closure |
new class {} |
object |
class@anonymous |
new class extends Foo {} |
object |
Foo@anonymous |
curl_init() |
resource |
resource (curl) |
curl_close($ch) |
resource (closed) |
resource (closed) |
Migracja zasobów do obiektów
Wartości typu resource pochodzą z czasów, gdy PHP nie posiadał jeszcze obiektów, a wręcz ich potrzebował. W ten sposób narodziły się zasoby. Dziś mamy obiekty i w porównaniu z zasobami znacznie lepiej współpracują z garbage collectorem, więc plan jest taki, aby stopniowo zastąpić je wszystkie obiektami.
Od PHP 8.0, zasoby images, curl joins, openssl, xml, etc. zostały zmienione na obiekty. W PHP 8.1, połączenia FTP, itp. będą podążać za nimi.
$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage // true
is_resource($res) // false - BC break
Te obiekty nie mają jeszcze żadnych metod, ani nie można ich bezpośrednio
instancjonować. Jak na razie jest to tylko kwestia pozbycia się
przestarzałych zasobów z PHP bez zmiany API. I dobrze, bo stworzenie dobrego
API to osobne i wymagające zadanie. Nikt nie życzy sobie tworzenia nowych
klas PHP takich jak SplFileObject z metodami o nazwach fgetc()
lub fgets()
.
PhpToken
Tokenizer i funkcje wokół token_get_all
również zostały
zmigrowane do obiektów. Tym razem nie chodzi o pozbycie się zasobów, ale
otrzymujemy pełnoprawny obiekt reprezentujący jeden token PHP.
<?php
$tokens = PhpToken::tokenize('<?php $a = 10;');
$token = $tokens[0]; // instance PhpToken
echo $token->id; // T_OPEN_TAG
echo $token->text; // '<?php'
echo $token->line; // 1
echo $token->getTokenName(); // 'T_OPEN_TAG'
echo $token->is(T_STRING); // false
echo $token->isIgnorable(); // true
Metoda isIgnorable()
zwraca true dla tokenów
T_WHITESPACE
, T_COMMENT
, T_DOC_COMMENT
,
oraz T_OPEN_TAG
.
Słabe mapy
Słabe mapy są związane z garbage collectorem, który zwalnia z pamięci wszystkie obiekty i wartości, które nie są już używane (tzn. nie ma zawierającej je zmiennej lub właściwości). Ponieważ wątki PHP są krótkotrwałe, a na naszych serwerach mamy dużo dostępnej pamięci, zazwyczaj w ogóle nie zajmujemy się kwestiami dotyczącymi efektywnego zwalniania pamięci. Jednak w przypadku dłużej działających skryptów są one niezbędne.
Obiekt WeakMap
jest podobny do SplObjectStorage
Oba
używają obiektów jako kluczy i pozwalają na przechowywanie pod nimi
dowolnych wartości. Różnica polega na tym, że WeakMap
nie
zapobiega zwolnieniu obiektu przez garbage collector. Tzn. Jeśli jedynym
miejscem, w którym obiekt obecnie istnieje, jest klucz w słabej mapie,
zostanie on usunięty z mapy i pamięci.
$map = new WeakMap;
$obj = new stdClass;
$map[$obj] = 'data for $obj';
dump(count($map)); // 1
unset($obj);
dump(count($map)); // 0
Do czego to jest dobre? Na przykład do cachowania. Miejmy metodę
loadComments()
, której przekazujemy artykuł na blogu, a ona
zwraca wszystkie jego komentarze. Ponieważ metoda jest wywoływana wielokrotnie
dla tego samego artykułu, stworzymy kolejną getComments()
, która
będzie buforować wynik pierwszej metody:
class Comments
{
private WeakMap $cache;
public function __construct()
{
$this->cache = new WeakMap;
}
public function getComments(Article $article): ?array
{
$this->cache[$article] ??= $this->loadComments($article);
return $this->cache[$article]
}
...
}
Chodzi o to, że gdy obiekt $article
zostanie zwolniony (na
przykład aplikacja rozpocznie pracę z innym artykułem), jego wpis również
zostanie zwolniony z pamięci podręcznej.
PHP JIT (Just in Time Compiler)
Być może wiesz, że PHP jest kompilowany do tzw. opcode, czyli niskopoziomowych instrukcji, które możesz zobaczyć na przykład tutaj, a które są wykonywane przez wirtualną maszynę PHP. A co to jest JIT? JIT może transparentnie skompilować PHP bezpośrednio do kodu maszynowego, który jest wykonywany bezpośrednio przez procesor, dzięki czemu wolniejsze wykonywanie przez maszynę wirtualną jest omijane.
JIT jest więc przeznaczony do przyspieszenia PHP.
Wysiłki mające na celu wdrożenie JIT do PHP sięgają 2011 roku i są wspierane przez Dmitry'ego Stogova. Od tego czasu wypróbował on 3 różne implementacje, ale żadna z nich nie trafiła do finalnego wydania PHP z trzech powodów: rezultatem nigdy nie był znaczący wzrost wydajności dla typowych aplikacji internetowych; komplikuje utrzymanie PHP (tzn. nikt poza Dmitrym tego nie rozumie 😉 ); istniały inne sposoby na poprawę wydajności bez konieczności używania JIT.
Skokowy wzrost wydajności obserwowany w PHP w wersji 7 był produktem ubocznym prac nad JIT, choć paradoksalnie nie został wdrożony. Dzieje się to dopiero teraz w PHP 8. Będę jednak powstrzymywał przesadne oczekiwania: prawdopodobnie nie zobaczysz żadnego przyspieszenia.
Dlaczego więc JIT wchodzi do PHP? Po pierwsze, inne sposoby na poprawę wydajności powoli się kończą, a JIT jest po prostu kolejnym krokiem. W zwykłych aplikacjach internetowych nie przynosi poprawy prędkości, ale znacznie przyspiesza np. obliczenia matematyczne. Otwiera to możliwość rozpoczęcia pisania takich rzeczy w PHP. W rzeczywistości możliwe byłoby wdrożenie niektórych funkcji bezpośrednio w PHP, które wcześniej wymagały bezpośredniej implementacji w C ze względu na szybkość.
JIT jest częścią rozszerzenia opcache
i jest włączany
razem z nim w php.ini (przeczytaj dokumentację
o tych czterech cyfrach):
zend_extension=php_opcache.dll
opcache.jit=1205 ; configuration using four digits OTRC
opcache.enable_cli=1 ; in order to work in the CLI as well
opcache.jit_buffer_size=128M ; dedicated memory for compiled code
To, że JIT jest uruchomiony, można sprawdzić np. w panelu informacyjnym Tracy Bar.
JIT działa bardzo dobrze, jeśli wszystkie zmienne mają jasno określone
typy i nie mogą się zmieniać nawet przy wielokrotnym wywoływaniu tego
samego kodu. Dlatego zastanawiam się, czy kiedyś w PHP dla zmiennych też
będziemy deklarować
typy: string $s = 'Bye, this is the end of the series';
Aby przesłać komentarz, proszę się zalogować