Ga naar de inhoud

Benchmarks

Phasis is bij dispatchgebonden JavaScript grofweg twee ordes van grootte trager dan V8. Dat plafond is de inherente prijs van een interpreter die in PHP is gehost, vergeleken met een native JIT-compiler. Voor wie de engine insluit, is de vraag niet "hoe verhoudt hij zich tot V8", maar "is hij snel genoeg voor wat ik doe?" Voor de meeste workloads waarbij de engine wordt ingesloten, is het antwoord ja.

Wanneer het plafond er niet toe doet

  • Sjablonen — SSR-achtige weergave van een doorsnee pagina in ruim minder dan 10 ms.
  • Validatieregels — door gebruikers aangeleverde JS voor formuliervalidatie zijn binnen microseconden klaar.
  • Gegevenstransformaties — array.map(transform) over honderden items is binnen tientallen milliseconden klaar.
  • Sandbox-runners — korte scripts van gebruikers met een hostlimiet van 1 seconde laten ruim voldoende speelruimte.

Wanneer het plafond er wel toe doet

  • Rekenintensieve binnenste lussen — beeldverwerking, bewerkingen op grote arrays, simulaties. Verplaats de berekeningen naar een native PHP-bibliotheek en stel die beschikbaar als hostfunctie.
  • Realtimeanimatie — alles wat 60 fps aanstuurt. Die JS hoort thuis in de client (browser, native app), niet in de ingesloten engine.
  • Herimplementaties van JS van algoritmen die veel berekeningen vergen — cryptografie, compressie, parsing. PHP heeft native tegenhangers die 1000× sneller zijn; gebruik die.

Het juiste denkmodel: Phasis is een lijmlaag voor logica in de stijl van JS, ingebed in applicaties in de stijl van PHP. De PHP-host voert de berekeningen uit; de JS-laag verzorgt configuratie, vertakkingen en kleine transformaties.

Waardoor de engine sneller is (dan een naïeve tree-walker)

  • Bytecode-VM — het grootste deel van de veelgebruikte dispatch loopt via de opcode-switch van VM::execute, niet via de AST.
  • Aangepaste aanroepstack — bij elke JS-aanroep blijft de status binnen VM::execute en wordt het frame van de aanroeper opgeslagen in een pool; er komt geen nieuw PHP-frame per JS-aanroep. Zie Bytecode-VM § Aangepaste aanroepstack.
  • Inlinecaches — een monomorfe cache per PC zorgt ervoor dat het lezen van een prototypemethode op een instantie (de vorm c.method()) de prototypeketen niet hoeft te doorlopen.
  • Generators met framesnapshots — eenvoudige generatorbodies pauzeren door de dispatchstatus vast te leggen in een heapobject, in plaats van over te gaan naar een PHP Fiber.
  • JsToPhp-transpiler — bodies met veel numerieke bewerkingen of lokale variabelen (waaronder obj.method(), new Ctor() en this.member-lezingen) worden gecompileerd naar PHP-closures, die de tracing-JIT van PHP 8.5 omzet naar native machinecode.
  • JSON.stringify-cache — JSON.stringify(o) slaat de uitvoer op het invoerobject op en invalideert die bij transitieve versie wijzigingen, waardoor het idioom JSON.parse(JSON.stringify(o)) voor diep klonen sneller wordt.

Uw eigen workload meten

Shell
php bench/run.php

Dit voert de microbench-suite uit die bij bench/microbench.js wordt geleverd — een handvol synthetische workloads (lusberekeningen, recursieve functies, objectaanmaak, aanroepen van prototypemethoden, array-push, JSON round-trip, enz.) — en drukt de mediane wandkloktijd per test af. Handig als snelle controle; de huidige cijfers worden bij elke CI-benchrun vastgelegd in BENCH.md.

Vervang voor uw eigen code het script:

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

Voor profilering op PHP-niveau werkt de profiler van Xdebug op precies dezelfde manier met Phasis als met elke andere PHP-applicatie. Voer een script uit met xdebug.mode=profile en bekijk de cachegrind-uitvoer om te zien welke Phasis\-klassen de meeste tijd kosten.

Routekaart

Wat de kloof met V8 zou verkleinen, ongeveer in volgorde van haalbaarheid:

  • Polymorfe inlinecaches — laat één LOAD_MEMBER-aanroeppunt 2-4 vormen cachen voordat het terugvalt op trage dispatch. Helpt code die heterogene ontvangers verwerkt.
  • yield* en try-with-yield op het pad met generators die framesnapshots gebruiken — deze vormen gaan nu via de Fiber-ondersteunde tree-walker. Voor snapshotondersteuning moet elke vorm expliciet als toestandsmachine worden afgehandeld (doorsturen van gedelegeerde iterators, handlerstack behouden tijdens pauzeren).
  • Async-generators en await op snapshots — de specificatie combineert het pauzeren van generators met de microtaskwachtrij. Zodra de bytecodebody zowel yield als await ondersteunt, zijn async-generators ook geschikt voor snapshots.
  • Type-inferentie van parameters in JsToPhp — funct parameters krijgen standaard het numerieke type, waardoor function (obj) { obj.method() } de compiler afbreekt. Objectgetypeerde parameters afleiden uit hun gebruik in de body zou methodeaanroepen op functieargumenten mogelijk maken zonder dat de ontvanger lokaal via new moet worden aangemaakt.
  • Directe opcache-emissie — sla de eval()-stap van JsToPhp over door opcodes rechtstreeks in opcache-indeling te schrijven. Dit is het meest ingrijpende punt; de andere kunnen binnen de bestaande architectuur worden uitgevoerd.

Niets hiervan is noodzakelijk voor de workloads waarbij Phasis wordt ingesloten.