Preskoči na vsebino

Primerjalni preizkusi

Phasis je pri obremenitvah JavaScript, omejenih s pošiljanjem, približno dva velikostna reda počasnejši od V8. Ta zgornja meja je neizogiben strošek tolmača, ki gostuje v PHP, v primerjavi z izvornim prevajalnikom JIT. Vprašanje za tistega, ki vgrajuje tolmač, ni »kako se primerja z V8«, temveč »ali je dovolj hiter za moje potrebe«. Za večino primerov vgradnje je odgovor pritrdilen.

Kdaj omejitev ni pomembna

  • Predloge — upodobitev običajne strani v slogu SSR traja precej manj kot 10 ms.
  • Pravila preverjanja — preverjanje obrazca z uporabniško določenim JS se konča v mikrosekundah.
  • Pretvorbe podatkov — array.map(transform) nad več sto elementi se zaključi v nekaj deset milisekundah.
  • Peskovniki za izvajanje — kratki skripti, ki jih poda uporabnik, ob omejitvi gostitelja na 1 sekundo pustijo veliko časovne rezerve.

Kdaj je omejitev pomembna

  • Računsko zahtevne notranje zanke — obdelava slik, redukcije velikih polj, simulacije. Računsko delo prenesite v izvorno knjižnico PHP in jo izpostavite kot funkcijo gostitelja.
  • Animacija v realnem času — vse, kar poganja 60 fps. To JS sodi v odjemalca (brskalnik, izvorna aplikacija), ne v vdelani pogon.
  • JS ponovne implementacije računsko zahtevnih algoritmov — kriptografija, stiskanje, razčlenjevanje. PHP ima izvorne ustreznice, ki so 1000× hitrejše; uporabite jih.

Ustrezen miselni model: Phasis je povezovalni sloj za logiko v obliki JS, vdelano v aplikacije v obliki PHP. Gostitelj PHP skrbi za računanje, sloj JS pa za konfiguracijo, razvejanje in manjše pretvorbe.

Zaradi česa je pogon hiter (v primerjavi z naivnim sprehajalnikom drevesa)

  • Navidezni stroj z bajtno kodo — večina vročih dispečerskih klicev poteka prek preklopa za operacijske kode v VM::execute, ne prek AST-ja.
  • Po meri izdelan sklad klicev — vsak klic JS ohrani stanje znotraj VM::execute in shrani okvir klicatelja v področje; za vsak klic JS ni novega okvira PHP. Glejte Navidezni stroj z bajtno kodo § Sklad klicev po meri.
  • Vgrajeni predpomnilniki — enomodni predpomnilnik za posamezni PC omogoča, da branje metode prototipa na primerku (oblika c.method()) preskoči sprehod po verigi prototipov.
  • Generatorji s posnetki okvirov — izvajanje preprostih teles generatorjev se prekine tako, da se stanje dispečiranja shrani v objekt na kopici, namesto da bi prečkalo vlakno PHP.
  • Transpiler JsToPhp — telesa, ki intenzivno uporabljajo števila ali lokalne spremenljivke (vključno z branji obj.method(), new Ctor() in this.member), se prevedejo v zaprtja PHP, ki jih sledilni JIT v PHP 8.5 prevede v izvorno strojno kodo.
  • Predpomnilnik JSON.stringify — JSON.stringify(o) predpomni svoj izhodni rezultat na vhodnem objektu s prehodnim razveljavljanjem različic, kar pospeši idiom globokega kloniranja JSON.parse(JSON.stringify(o)).

Merjenje lastne delovne obremenitve

Lupina
php bench/run.php

S tem zaženete zbirko mikroprimerjalnih testov, priloženo pod bench/microbench.js — nekaj sintetičnih delovnih obremenitev (aritmetika v zankah, rekurzivne funkcije, ustvarjanje objektov, klici metod prototipov, dodajanje v polje, povratno pretvarjanje JSON itd.) — in izpišete srednji stenski čas za vsak test. Uporabno za osnovni pregled; trenutne številke se ob vsakem izvajanju primerjalnih testov CI zapišejo v BENCH.md.

Za svojo kodo lahko uporabite poljubni skript:

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

Za profiliranje na ravni PHP profiler iz Xdebug deluje z Phasis enako kot s katero koli drugo aplikacijo PHP. Zaženite skript z xdebug.mode=profile in preglejte izhod cachegrind, da ugotovite, kateri razredi Phasis\ prevladujejo.

Načrt razvoja

Kaj bi najbolj zmanjšalo vrzel do V8, razvrščeno približno po izvedljivosti:

  • Polimorfni vgrajeni predpomnilniki — omogočite, da eno mesto klica LOAD_MEMBER shrani 2–4 predpomnjene oblike, preden preklopi na počasno dispečiranje. Pomaga pri kodi, ki obdeluje heterogene prejemnike.
  • yield* in try-with-yield na poti generatorjev s posnetki okvirov — te oblike se trenutno preusmerijo na sprehajalnik dreves, podprt z vlakni. Preden bodo primerne za posnetke, je treba izrecno obravnavati stanje (posredovanje iteratorja delegata, ohranitev sklada obravnavalnikov med prekinitvijo).
  • Asinhroni generatorji in await na posnetkih — specifikacija združuje prekinitve generatorjev z vrsto mikroopravil. Ko telo bajtne kode podpira oboje, yield in await, bodo tudi asinhroni generatorji primerni za posnetke.
  • Sklepanje o vrstah parametrov v JsToPhp — parametri funkcij so privzeto številskega tipa, zato function (obj) { obj.method() } prekine prevajanje. Sklepanje o objektnih vrstah parametrov na podlagi njihove uporabe v telesu bi omogočilo klice metod na argumentih funkcije, ne da bi bilo treba prejemnik lokalno ustvariti z new.
  • Neposredno ustvarjanje opkod v opcache — preskočite korak eval() v JsToPhp tako, da neposredno zapišete opkode v obliki opcache. To je največji poseg; druge izboljšave je mogoče izvesti v obstoječi arhitekturi.

Nobena od teh izboljšav ni potrebna za vdelane delovne obremenitve, ki jih podpira Phasis.