Přejít k obsahu

Výkonnostní testy

Phasis je u úloh vázaných na volání zhruba o dva řády pomalejší než V8JavaScript. Tento limit je inherentní cenou interpretru hostovaného v PHP oproti nativnímu JIT kompilátoru. Pro toho, kdo jej vkládá, není otázkou „jak si vede ve srovnání s V8“ — ale „je dost rychlý pro to, co dělám“. Pro většinu úloh s vloženým interpretem je odpověď ano.

Kdy na limitu nezáleží

  • Šablony — vykreslení typické stránky ve stylu SSR trvá výrazně méně než 10 ms.
  • Validační pravidla — uživatelem zadané JS pro ověřování formuláře se dokončí v řádu mikrosekund.
  • Transformace dat — array.map(transform) nad stovkami položek se dokončí v řádu desítek milisekund.
  • Spouštěče v sandboxu — krátké skripty dodané uživatelem mají při limitu hostitele 1 sekunda dostatečnou rezervu.

Kdy na limitu záleží

  • Výpočetně náročné vnitřní smyčky — zpracování obrázků, redukce velkých polí, simulace. Přesuňte výpočty do nativní knihovny PHP a zpřístupněte ji jako hostitelskou funkci.
  • Animace v reálném čase — cokoli, co má běžet při 60 fps. Tato zátěž JS patří do klienta (prohlížeče nebo nativní aplikace), ne do vloženého enginu.
  • Opětovné implementace algoritmů náročných na výpočty v JS — kryptografie, komprese, parsování. PHP má nativní ekvivalenty, které jsou 1000× rychlejší; použijte je.

Vhodný způsob uvažování: Phasis je propojovací vrstva pro logiku psanou v duchu JS vloženou do aplikací psaných v duchu PHP. Hostitel PHP zajišťuje výpočty; vrstva JS zajišťuje konfiguraci, větvení a drobné transformace.

Díky čemu je engine rychlý (ve srovnání s naivním stromovým interpretem)

  • VM s bajtkódem — většina často prováděných volání prochází přepínačem operačních kódů v VM::execute, nikoli AST.
  • Vlastní zásobník volání — každé volání JS uchovává stav uvnitř VM::execute a ukládá rámec volajícího do fondu; při každém volání JS nevzniká nový rámec PHP. Viz Virtuální stroj s bajtkódem § Vlastní zásobník volání.
  • Mezipaměti inline — monomorfní mezipaměť pro každé PC umožňuje při čtení metody prototypu instance (vzor c.method()) přeskočit procházení řetězce prototypů.
  • Generátory se snímkem rámce — jednoduchá těla generátorů se pozastaví zachycením stavu zpracování do objektu na haldě, takže není nutné přecházet přes Fiber PHP.
  • Transpiler JsToPhp — těla s převahou číselných operací a lokálních proměnných (včetně čtení obj.method(), new Ctor() a this.member) se kompilují do uzávěrů PHP, které trasovací JIT kompilátor v PHP 8.5 převádí na nativní strojový kód.
  • Mezipaměť JSON.stringify — JSON.stringify(o) ukládá výstup do vstupního objektu a transitive invalidací verzí zrychluje idiom hlubokého klonování JSON.parse(JSON.stringify(o)).

Měření vlastního zatížení

Shell
php bench/run.php

Spustí sadu mikrovýkonnostních testů dodávanou v bench/microbench.js — několik syntetických zátěží (aritmetika ve smyčce, rekurzivní funkce, vytváření objektů, volání metod prototypu, přidávání do pole, převod JSON tam a zpět atd.) — a vypíše medián uplynulého času pro každý test. Hodí se jako základní kontrola; aktuální výsledky se při každém běhu CI testů výkonu ukládají do BENCH.md.

U vlastního kódu nahraďte skript libovolným jiným:

Shell
php -d xdebug.mode=off bench/run.php path/to/your-script.js

Pro profilování na úrovni PHP funguje profiler Xdebug s Phasis stejně jako s jakoukoli jinou aplikací PHP. Spusťte skript pod xdebug.mode=profile a prohlédněte si výstup cachegrind, abyste zjistili, které třídy Phasis\ převažují.

Plán dalšího vývoje

Co by v přibližném pořadí podle náročnosti nejvíc zmenšilo rozdíl ve výkonu V8:

  • Polymorfní mezipaměti inline — umožnit jednomu místu volání LOAD_MEMBER uchovávat 2–4 struktury před přechodem k pomalejšímu zpracování. Pomůže kódu, který zpracovává různorodé příjemce volání.
  • yield* a try-with-yield v cestě generátorů se snímkem rámce — tyto tvary nyní přecházejí ke stromovému interpretu založenému na Fiber. Než budou bezpečné pro snímkování, každý vyžaduje výslovné zpracování stavovým automatem (předávání iterátoru delegáta, zachování zásobníku obslužných rutin při pozastavení).
  • Asynchronní generátory a await ve snímcích — specifikace kombinuje pozastavení generátoru s frontou mikroúloh. Až tělo bajtkódu podpoří yield i await, budou pro snímkování vhodné také asynchronní generátory.
  • Odvození typů parametrů v JsToPhp — parametry funkcí mají ve výchozím stavu číselný typ, takže function (obj) { obj.method() } zastaví kompilátor. Odvozování objektových typů parametrů podle toho, jak se používají v těle funkce, by umožnilo volání metod nad argumenty funkcí bez nutnosti vytvářet příjemce lokálně pomocí new.
  • Přímé generování opkódů pro opcache — přeskočit krok eval() v JsToPhp přímým zápisem opkódů ve formátu opcache. Nejnáročnější položka; ostatní lze zvládnout v rámci stávající architektury.

Nic z toho není potřeba pro integrační zátěže, na které cílí Phasis.