
# Testy wydajnościowe

Phasis jest **mniej więcej dwa rzędy wielkości wolniejsze niż V8** przy ograniczeniu przepustowości przez dyspozycję JavaScript. Ten limit wynika z nieuniknionego kosztu interpretera działającego w środowisku PHP w porównaniu z natywnym kompilatorem JIT. Pytanie dla osoby osadzającej nie brzmi „jak wypada w porównaniu z V8”, lecz „czy jest wystarczająco szybki do tego, co robię”. W przypadku większości obciążeń związanych z osadzaniem odpowiedź brzmi: tak.

## Kiedy ograniczenie nie ma znaczenia

- **Szablony** — renderowanie typowej strony w stylu SSR zajmuje znacznie mniej niż 10 ms.
- **Reguły walidacji** — dostarczony przez użytkownika JS do walidacji formularza wykonuje się w mikrosekundach.
- **Transformacje danych** — `array.map(transform)` dla setek elementów kończy się w dziesiątkach milisekund.
- **Uruchamianie w piaskownicy** — krótkie skrypty użytkownika z limitem hosta równym 1 sekundzie pozostawiają duży zapas czasu.

## Kiedy ograniczenie ma znaczenie

- **Intensywne obliczeniowo pętle wewnętrzne** — przetwarzanie obrazów, redukcje dużych tablic, symulacje. Przenieś obliczenia do natywnej biblioteki PHP i udostępnij je jako funkcję hosta [host](https://phasis.inline0.dev/pl/docs/interop/host-functions).
- **Animacje w czasie rzeczywistym** — wszystko, co wymaga 60 fps. Taka logika JS powinna działać po stronie klienta (w przeglądarce lub aplikacji natywnej), a nie w osadzonym silniku.
- **Reimplementacje JS algorytmów wymagających dużych obliczeń** — kryptografia, kompresja, parsowanie. PHP ma natywne odpowiedniki działające 1000× szybciej; użyj ich.

Przyjmij następujący model: Phasis to **warstwa łącząca** logikę w stylu JS osadzoną w aplikacjach w stylu PHP. Host PHP obsługuje obliczenia; warstwa JS — konfigurację, rozgałęzienia i niewielkie transformacje.

## Co sprawia, że silnik jest szybki (w porównaniu z naiwnym walkerem drzewa)

- **Maszyna wirtualna kodu bajtowego** — większość gorących wywołań przechodzi przez przełącznik kodów operacji `VM::execute`, a nie przez AST.
- **Własny stos wywołań** — każde wywołanie JS zachowuje stan w `VM::execute` i zapisuje ramkę wywołującego w puli; dla każdego wywołania JS nie powstaje nowa ramka PHP. Zob. [Własny stos wywołań maszyny wirtualnej kodu bajtowego](https://phasis.inline0.dev/pl/docs/advanced/bytecode-vm).
- **Pamięci podręczne inline** — monomorficzna pamięć podręczna dla każdego PC pozwala uniknąć przechodzenia łańcucha prototypów przy odczycie metody prototypu z instancji (wzorzec `c.method()`).
- **Generatory z migawką ramki** — proste ciała generatorów zawieszają działanie, zapisując stan dyspozytora w obiekcie na stercie, zamiast przechodzić przez Fiber PHP.
- **Transpiler JsToPhp** — ciała intensywnie korzystające z liczb lub zmiennych lokalnych (w tym odczyty `obj.method()`, `new Ctor()` i `this.member`) są kompilowane do domknięć PHP, które śledzący JIT w wersji PHP 8.5 obniża do natywnego kodu maszynowego.
- **Pamięć podręczna JSON.stringify** — `JSON.stringify(o)` zapisuje wynik w obiekcie wejściowym i unieważnia go pośrednio na podstawie wersji, przyspieszając idiom głębokiego klonowania `JSON.parse(JSON.stringify(o))`.

## Pomiar własnego obciążenia

```bash
php bench/run.php
```

Uruchamia to zestaw mikrotestów wydajnościowych z pakietu `bench/microbench.js` — kilka syntetycznych obciążeń (arytmetyka w pętli, funkcje rekurencyjne, tworzenie obiektów, wywołania metod prototypu, dodawanie elementów do tablicy, cykl JSON i inne) — i wypisuje medianę czasu rzeczywistego dla każdego testu. Przydatne jako szybka kontrola; aktualne wyniki są zapisywane w [`BENCH.md`](https://github.com/inline0/phasis/blob/main/BENCH.md) przy każdym uruchomieniu testów wydajnościowych CI.

Aby sprawdzić własny kod, podmień skrypt:

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

Do profilowania na poziomie PHP profiler Xdebug działa z Phasis tak samo jak z każdą inną aplikacją PHP. Uruchom skrypt pod `xdebug.mode=profile` i sprawdź dane wyjściowe cachegrind, aby zobaczyć, które klasy `Phasis\` dominują.

## Plan rozwoju

Co najbardziej zmniejszyłoby różnicę w V8, w przybliżonej kolejności od najłatwiejszych do najtrudniejszych:

- **Polimorficzne pamięci podręczne inline** — pozwalają pojedynczemu miejscu wywołania `LOAD_MEMBER` przechowywać 2–4 kształty w pamięci podręcznej, zanim nastąpi przejście do wolnego mechanizmu dispatch. Pomaga to w przypadku kodu przetwarzającego niejednorodne odbiorniki.
- **`yield*` i try-with-yield w ścieżce generatora z migawką ramki** — te kształty są obecnie obsługiwane przez oparty na Fiber walker drzewa. Każdy wymaga jawnej obsługi jako maszyna stanów (przekazywania delegowanego iteratora, zachowania stosu obsługi między zawieszeniami), zanim będzie bezpieczny dla migawek.
- **Generatory asynchroniczne i `await` w migawkach** — specyfikacja łączy zawieszenie generatora z kolejką mikrozadań. Gdy treść bajtkodu będzie obsługiwać zarówno `yield`, jak i `await`, generatory asynchroniczne również będą obsługiwać migawki.
- **Wnioskowanie o typach parametrów w JsToPhp** — parametry funkcji domyślnie mają typ numeryczny, więc `function (obj) { obj.method() }` powoduje przerwanie kompilacji. Wnioskowanie o typie obiektowym parametrów na podstawie sposobu ich użycia w treści funkcji umożliwiłoby wywoływanie metod na argumentach funkcji bez konieczności lokalnego tworzenia odbiornika za pomocą `new`.
- **Bezpośrednie emitowanie opcache** — pominięcie kroku `eval()` w JsToPhp przez bezpośrednie zapisywanie kodów operacji w formacie opcache. To najtrudniejszy element; pozostałe można zrealizować w ramach istniejącej architektury.

Żadne z tych rozwiązań nie jest konieczne w przypadku obciążeń związanych z osadzaniem, które obsługuje Phasis.
