Preskočiť na obsah

Výkonnostné testy

Phasis je pri úlohách závislých od dispečingu JavaScript približne o dva rády pomalší než V8. Tento limit vyplýva zo samotnej podstaty interpretera hostovaného v PHP v porovnaní s natívnym JIT kompilátorom. Vkladateľ sa nemusí pýtať „ako si vedie v porovnaní s V8“, ale „je dosť rýchly na to, čo potrebujem“. Pri väčšine úloh s vloženým enginom je odpoveď áno.

Kedy na limite nezáleží

  • Šablóny — vykreslenie typickej stránky v štýle SSR trvá výrazne menej než 10 ms.
  • Pravidlá validácie — validácia formulára pomocou používateľom zadaného JS sa dokončí za mikrosekundy.
  • Transformácie údajov — array.map(transform) nad stovkami položiek sa dokončí za desiatky milisekúnd.
  • Spúšťače v izolovanom prostredí — pri krátkych skriptoch dodaných používateľom a limite hostiteľa 1 sekunda zostáva dostatočná časová rezerva.

Kedy na limite záleží

  • Výpočtovo náročné vnútorné slučky — spracovanie obrázkov, redukcie veľkých polí, simulácie. Presuňte výpočty do natívnej knižnice PHP a sprístupnite ju ako hostiteľskú funkciu.
  • Animácia v reálnom čase — všetko, čo má bežať pri 60 snímkach za sekundu. To JS patrí do klienta (prehliadača, natívnej aplikácie), nie do vloženého enginu.
  • JS opätovné implementácie výpočtovo náročných algoritmov — kryptografia, kompresia, parsovanie. PHP má natívne ekvivalenty, ktoré sú 1000× rýchlejšie; použite ich.

Správny mentálny model: Phasis je prepojovacia vrstva pre logiku v štýle JS vloženú do aplikácií v štýle PHP. Hostiteľ PHP zabezpečuje výpočty; vrstva JS sa stará o konfiguráciu, vetvenie a malé transformácie.

Vďaka čomu je engine rýchly (v porovnaní s naivným stromovým interpreterom)

  • VM s bajtkódom — väčšina horúceho dispečingu prebieha cez prepínač opkódov VM::execute, nie cez AST.
  • Vlastný zásobník volaní — každé volanie JS udržiava stav v VM::execute a ukladá rámec volajúceho do fondu; pri každom volaní JS sa nevytvára nový rámec PHP. Pozrite si Virtuálny stroj bajtkódu § Vlastný zásobník volaní.
  • Inline cache — monomorfná cache pre každé PC umožňuje pri čítaní metódy prototypu na inštancii (vzor c.method()) preskočiť prehľadávanie reťazca prototypov.
  • Generátory so snímkou rámca — jednoduché telá generátorov sa pozastavia zachytením stavu dispečingu do objektu na halde namiesto prechodu cez Fiber PHP.
  • Transpilátor JsToPhp — telá funkcií s číselnými hodnotami a množstvom lokálnych premenných (vrátane čítaní obj.method(), new Ctor() a this.member) sa kompilujú do uzáverov PHP, ktoré trasovací JIT nástroja PHP 8.5 prekladá na natívny strojový kód.
  • Cache JSON.stringify — JSON.stringify(o) ukladá výstup do vstupného objektu a používa tranzitívnu invalidáciu verzií, čím zrýchľuje idiom hlbokého klonovania JSON.parse(JSON.stringify(o)).

Meranie vlastnej záťaže

Shell
php bench/run.php

Týmto sa spustí sada mikrobenchmarkov dodávaná s bench/microbench.js — niekoľkými syntetickými úlohami (aritmetika v cykloch, rekurzívne funkcie, vytváranie objektov, volania metód prototypu, pridávanie prvkov do poľa, spiatočný prevod JSON a podobne) — a vypíše medián času vykonania každého testu. Je to užitočná kontrola; aktuálne výsledky sa pri každom spustení benchmarkov CI ukladajú do BENCH.md.

Pre vlastný kód môžete použiť ľubovoľný skript:

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

Na profilovanie na úrovni PHP funguje profiler nástroja Xdebug s Phasis rovnako ako s akoukoľvek inou aplikáciou PHP. Spustite skript pomocou xdebug.mode=profile a vyskúmajte výstup cachegrind, aby ste zistili, ktoré triedy Phasis\ zaberajú najviac času.

Plán vývoja

Čo by najviac zmenšilo rozdiel v V8, zoradené približne podľa náročnosti realizácie:

  • Polymorfné inline cache — umožniť jednému volaciemu miestu LOAD_MEMBER uchovávať 2-4 tvary v cache, kým sa prejde na pomalšie spracovanie. Pomáha kódu, ktorý spracúva rôznorodé prijímajúce objekty.
  • yield* a try-with-yield v ceste generátora so snímkou rámca — tieto konštrukcie sa momentálne presmerujú do stromového interpretera založeného na Fiber. Pred bezpečným vytváraním snímok si každá vyžaduje explicitné spracovanie stavového automatu (posúvanie delegovania iterátora, zachovanie zásobníka obslužných rutín počas pozastavenia).
  • Asynchrónne generátory a await v snímkach — špecifikácia kombinuje pozastavenie generátora s frontom mikroúloh. Keď telo bajtkódu podporí yield aj await, asynchrónne generátory budú tiež kompatibilné so snímkami.
  • Odvodzovanie typov parametrov v JsToPhp — parametre funkcií majú predvolene číselný typ, preto kompilátor pri function (obj) { obj.method() } zlyhá. Odvodenie parametrov objektového typu podľa ich použitia v tele funkcie by sprístupnilo volania metód nad argumentmi funkcie bez toho, aby sa prijímajúci objekt musel vytvoriť lokálne pomocou new.
  • Priame generovanie opkódov opcache — preskočenie kroku eval() transpilátora JsToPhp priamym zapisovaním opkódov vo formáte opcache. Najnáročnejšia položka; ostatné možno realizovať v rámci existujúcej architektúry.

Žiadna z týchto zmien nie je potrebná pre úlohy s vloženým enginom, na ktoré je Phasis určený.