PHP 8.0: Nové funkce, třídy a JIT (4/4)

před měsícem od David Grudl     edit

Vyšlo PHP verze 8.0. Je tak nadupané novinkami, jako nebyla žádná verze předtím. Jejich představení si vyžádalo rovnou čtyři samostatné články. V tomto posledním se podíváme na nové funkce a třídy a představíme si Just in Time Compiler.

Nové funkce

Standardní knihovna PHP disponuje stovkami funkcí a ve verzi 8.0 se objevilo šest nových. Vypadá to jako málo, ale většina z nich zaceluje slabá místa jazyka. Což hezky koresponduje z vyzněním celé verze 8.0, která dotahuje a konsoliduje PHP jako žádná verze předtím. Přehled všech nových funkcí i metod najdete v migration guide.

str_contains() str_starts_with() str_ends_with()

Funkce pro zjištění, zda řetězec začíná, končí nebo v sobě obsahuje podřetězec.

if (str_contains('Nette', 'te')) {
	...
}

Společně s příchodem této trojice PHP definuje, jak zacházet s prázdným řetězcem při vyhledávání, podle čehož se řídí i všechny ostatní související funkce, a to tak, že prázdný řetězec se nachází všude:

str_contains('Nette', '')     // true
str_starts_with('Nette', '')  // true
strpos('Nette', '')           // 0 (dříve false)

Díky tomu je chování trojice zcela identické k obdobám v Nette:

str_contains()      # Nette\Utils\String::contains()
str_starts_with()   # Nette\Utils\String::startsWith()
str_ends_with()     # Nette\Utils\String::endsWith()

Proč jsou tyhle funkce tak důležité? Standardní knihovny všech jazyků jsou vždy zatěžkány historickým vývojem a nelze se ubránit vzniku nekonzistencí a přešlapů. Ale zároveň jde o vizitku toho kterého jazyka. Je s podivem, když u 25 let starého PHP chybí funkce pro tak základní operace, jako je vracení prvního či posledního prvku z pole, escapování HTML bez podrazů (htmlspecialchars neescapuje apostrof), nebo právě vyhledávání řetězce v řetězci. Že to jde nějak obejít neobstojí, protože výsledkem pak není čitelný a srozumitelný kód. Je to ponaučení pro všechny autory API. Když vidíte, že značnou část dokumentace funkce zabírá vysvětlení záludností (jako třeba návratové hodnoty strpos), je to jasný signál knihovnu upravit a doplnit právě str_contains.

get_debug_type()

Nahrazuje již zastaralé get_type(). Místo dlouhých typů jako integer vrací dnes užívané int, v případě objektů vrací rovnou 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)

Objektivizace zdrojů

Hodnoty typu resource pocházejí z dob, kdy ještě PHP nemělo objekty, ale vlastně je potřebovalo. Tak přišly na svět resources. Dnes objekty máme a oproti resources fungují daleko lépe s garbage kolektorem, takže v plánu je postupně všechny nahradit za objekty.

Od PHP 8.0 se mění na objekty resources obrázků, curl spojení, openssl, xml, apod.. V PHP 8.1 přijdou na řadu FTP spojení atd.

$res = imagecreatefromjpeg('image.jpg');
$res instanceof GdImage  // true
is_resource($res)        // false - BC break

Tyto objekty nemají zatím žádné metody, ani nemůžete přímo vytvářet jejich instance. Jde zatím skutečně jen o to dostat z PHP zastaralé resources beze změny API. A to je dobře, protože vytvoření dobrého API je samostatný a náročný úkol. Nikdo si nepřeje, aby v PHP vznikly další třídy jako SplFileObject s metodami pojmenovanými fgetc() nebo fgets().

PhpToken

Do objektů se přesouvá i tokenizér a tedy funkce kolem token_get_all. Tentokrát nejde o zbavování se resources, ale dostáváme plnohodnotný objekt představující jeden PHP token.

<?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() vrací true pro tokeny T_WHITESPACE, T_COMMENT, T_DOC_COMMENT a T_OPEN_TAG.

Weak maps

Weak mapy souvisí s gargabe kolektorem, který z paměti uvolňuje všechny objekty a hodnoty, které se už nepoužívají (tj. není žádná používaná proměnná, či property, která by je obsahovala). Jelikož život PHP vlákna je jepičí a paměti máme dnes na serverech k dispozici dost, otázky kolem efektivního uvolňování paměti zpravidla vůbec neřešíme. Ale u dlouhodoběji běžících skriptů jsou zásadní.

Objekt WeakMap je podobný SplObjectStorage V obou se jako klíče používají objekty a umožňují pod nimi ukládání libovolných hodnot. Rozdíl je v tom, že WeakMap nezabrání tomu, aby byl objekt uvolněn garbage kolektorem. Tj. pokud jediným místem, kde se ještě objekt vyskytuje, je klíč ve weak mapě, bude z mapy i paměti odstraněn.

$map = new WeakMap;
$obj = new stdClass;
$map[$obj]  = 'data for $obj';

dump(count($map));  // 1
unset($obj);
dump(count($map));  // 0

K čemu je to dobré? Třeba pro kešování. Mějme metodu loadComments(), které předáme článek na blogu a ona vrátí všechny jeho komentáře. Protože se metoda volá se stejným článkem opakovaně, vytvoříme si ještě getComments(), která bude výsledek první metody kešovat:

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]
	}

	...
}

Vtip je v tom, že ve chvíli, kdy se uvolní objekt $article (třeba aplikace začne pracovat s jiným článkem), uvolní se i jeho položka z cache.

PHP JIT (Just in Time Compiler)

Možná víte, že PHP se kompiluje do tzv. opcode, což jsou low-level instrukce, které si můžete prohlédnout třeba tady a které vykonává virtuální stroj PHP. A co je JIT? JIT dokáže transparentně kompilovat PHP přímo do strojového kódu, který vykonává přímo procesor, takže se obejde pomalejší vykonávání virtuálním strojem.

JIT má tedy zrychlit PHP.

Snaha implementovat JIT do PHP sahá až do roku 2011 a stojí za ní Dmitry Stogov. Od té doby vyzkoušel 3 různé implementace, ale žádná z nich se nedostala do ostrého PHP a to z těchto důvodů: výsledkem nikdy nebyl žádný podstatný nárůst výkonu pro typické webové aplikace; komplikuje údržbu PHP (tj. nikdo kromě Dmitryho tomu nerozumí 😉); existovaly jiné cesty, jak zlepšit výkon, aniž by se muselo používat JIT.

Skokové zvýšení výkonu PHP ve verzi 7 bylo vedlejším produktem práce na JIT, byť k jeho nasazení paradoxně nedošlo. K tomu dochází až v PHP 8. Ale rovnou budu brzdit přehnaná očekávání: pravděpodobně žádné zrychlení nezaznamenáte.

Proč teda vstupuje JIT do PHP? Jednak jiné cesty pro zlepšení výkonu pomalu docházejí a JIT je prostě na řadě. V běžných webových aplikacích sice zrychlení nepřináší, ale zásadně zrychluje třeba matematické výpočty. Otevírá se tak možnost začít tyhle věci v PHP psát. A vlastně by tak bylo možné přímo v PHP implementovat funkce, které dosud vyžadovaly implementaci přímo v C kvůli rychlosti.

JIT je součástí rozšíření opcache a zapíná se společně s ním v php.ini (přečtěte si dokumentaci k oné čtveřici číslic):

zend_extension=php_opcache.dll
opcache.jit=1205              ; konfigurace pomocí čtveřice OTRC
opcache.enable_cli=1          ; aby fungovalo i v CLI
opcache.jit_buffer_size=128M  ; vyhrazená paměť pro zkompilovaný kód

Že JIT běží se dozvíte například v informačním panelu v Tracy Baru.

JIT velmi dobře funguje tehdy, pokud všechny proměnné mají jasně dané typy a nemohou se měnit při opakovaném volání stejného kódu. Jsem proto zvědav, jestli jednou budeme v PHP deklarovat typy také u proměnných: string $s = 'Ahoj, tohle je závěr seriálu';

Komentáře (RSS)

  1. Díky

    před měsícem
  2. Akorát bych ještě doplnil, že pro získání prvního/posledního prvku pole se v PHP používají funkce end()/reset() a pokud jsi myslel klíče tak array_key_first()/array_key_last()

    před měsícem · replied [3] David Grudl
  3. #2 Polki To je právě jen vedlejší efekt těch funkcí, proto mají i tak nevhodné pojmenování a fungují jen s proměnnou, protože parametr je referencí. V PHP chybí array_first()/array_last().

    před měsícem
  4. To strpos se mi zdá nějaké divné..
    PHP 7.4.13
    var_dump(strpos(‚Nette je super‘, ‚Nette‘));
    int(0)
    var_dump(strpos(‚Allahváč Nabhar‘, ‚Nette‘));
    bool(false)

    Takže PHP 8 mi v druhém případě vrátí nulu? Takže mi vlastně řekne, že ten hledaný string, který se v textu nenachází začíná na prvním znaku? Toto by byl tedy brutální BC break, nějak mi uniká nakýkoli důvot to, udělat. Částečně bych pochopil null, ale i tak vzhledem k častéu využití jako náhrady za str_contains stylem if(strpos(‚x‘, ‚y‘) !== false)… to musí rozbít obrovskou spoustu věcí..

    před měsícem
  5. Aha, tak jsem to zkusil s php 8.0.0 a strpos vrátí false.
    To mi nedošlo, že ten int to tam dá jen při hledání prázdného stringu..

    před měsícem
  6. Tohle taky stojí za zmínku, to mi mnohokrát chybělo:

    Sorting functions are now stable, which means that equal-comparing elements will retain their original order.

    před měsícem

Chcete-li odeslat komentář, přihlaste se