
# Tests de performance

Phasis est **environ cent fois plus lent que V8** pour les charges JavaScript limitées par la distribution. Ce plafond est le coût inhérent d’un interpréteur hébergé par PHP par rapport à un compilateur JIT natif. Pour un intégrateur, la question n’est pas « comment se compare-t-il à V8 ? », mais « est-il assez rapide pour ce que je fais ? ». Pour la plupart des usages embarqués, la réponse est oui.

## Quand le plafond importe peu

- **Génération de modèles** — rendu SSR d’une page typique en moins de 10 ms.
- **Règles de validation** — le JS fourni par l’utilisateur pour valider un formulaire s’exécute en quelques microsecondes.
- **Transformations de données** — `array.map(transform)` sur des centaines d’éléments s’effectue en quelques dizaines de millisecondes.
- **Exécution en bac à sable** — les scripts courts fournis par l’utilisateur avec une limite hôte d’une seconde laissent une grande marge.

## Quand le plafond importe

- **Boucles internes gourmandes en calcul** — traitement d’images, réductions de grands tableaux, simulations. Déplacez les calculs vers une bibliothèque native PHP et exposez-la comme [fonction hôte](https://phasis.inline0.dev/fr/docs/interop/host-functions).
- **Animation en temps réel** — tout ce qui vise 60 fps. Cette logique JS doit se trouver côté client (navigateur, application native), pas dans le moteur intégré.
- **Réimplémentations en JS d’algorithmes coûteux en calcul** — cryptographie, compression, analyse syntaxique. PHP propose des équivalents natifs 1000× plus rapides ; utilisez-les.

Le bon modèle mental : Phasis est une **couche de liaison** pour la logique de type JS intégrée à des applications de type PHP. L’hôte PHP gère les calculs ; la couche JS gère la configuration, les branchements et les petites transformations.

## Ce qui rend le moteur rapide (par rapport à un parcours d’arbre naïf)

- **VM à bytecode** — la plupart des traitements de dispatch à chaud passent par le switch d’opcodes de `VM::execute`, et non par l’AST.
- **Pile d’appels personnalisée** — chaque appel JS conserve son état dans `VM::execute` et sauvegarde la frame de l’appelant dans un pool ; aucune nouvelle frame PHP par appel JS. Voir [VM à bytecode § Pile d’appels personnalisée](https://phasis.inline0.dev/fr/docs/advanced/bytecode-vm).
- **Caches inline** — un cache monomorphe par PC permet aux lectures de méthodes de prototype sur une instance (la forme `c.method()`) d’éviter le parcours de la chaîne de prototypes.
- **Générateurs à instantané de frame** — les corps de générateur simples suspendent leur exécution en capturant l’état du dispatch dans un objet du tas, sans passer par une Fiber PHP.
- **Transpileur JsToPhp** — les corps riches en calculs numériques ou en variables locales (y compris les lectures `obj.method()`, `new Ctor()` et `this.member`) sont compilés en closures PHP, que le JIT de traçage de PHP 8.5 convertit en code machine natif.
- **Cache JSON.stringify** — `JSON.stringify(o)` met son résultat en cache sur l’objet d’entrée, avec invalidation transitive des versions, ce qui accélère l’idiome de clonage profond `JSON.parse(JSON.stringify(o))`.

## Mesurer votre propre charge de travail

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

Cette commande exécute la suite de microbenchmarks fournie dans `bench/microbench.js` — quelques charges synthétiques (calculs dans des boucles, fonctions récursives, création d’objets, appels de méthodes de prototype, ajout à un tableau, aller-retour JSON, etc.) — puis affiche le temps mural médian de chaque test. Pratique pour vérifier que tout fonctionne ; les chiffres actuels sont enregistrés dans [`BENCH.md`](https://github.com/inline0/phasis/blob/main/BENCH.md) à chaque exécution des benchmarks de CI.

Pour tester votre propre code, remplacez le script :

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

Pour profiler au niveau PHP, le profileur de Xdebug fonctionne avec Phasis comme avec n’importe quelle application PHP. Exécutez un script sous `xdebug.mode=profile`, puis examinez la sortie de cachegrind pour voir quelles classes `Phasis\` dominent.

## Feuille de route

Voici, par ordre approximatif de faisabilité, les pistes qui réduiraient le plus l’écart de V8 :

- **Caches inline polymorphes** — permettre à un même site d’appel `LOAD_MEMBER` de conserver en cache 2 à 4 formes avant de revenir au dispatch lent. Utile pour le code qui traite des récepteurs hétérogènes.
- **`yield*` et try-with-yield pour les générateurs à instantané de frame** — ces formes passent actuellement par l’interpréteur arborescent basé sur Fiber. Chacune nécessite une gestion explicite par machine à états (transmission des itérateurs délégués, pile de gestionnaires conservée pendant la suspension) avant de pouvoir prendre en charge les instantanés.
- **Générateurs asynchrones et `await` sur les instantanés** — la spécification combine la suspension des générateurs avec la file des microtâches. Une fois que le corps en bytecode prendra en charge `yield` et `await`, les générateurs asynchrones pourront eux aussi utiliser les instantanés.
- **Inférence des types de paramètres dans JsToPhp** — les paramètres de fonction sont de type numérique par défaut, si bien que `function (obj) { obj.method() }` fait abandonner le compilateur. Déduire les paramètres de type objet à partir de leur utilisation dans le corps permettrait d’appeler des méthodes sur les arguments de fonction sans exiger que le récepteur soit créé localement via `new`.
- **Émission directe d’opcodes opcache** — éviter l’étape `eval()` de JsToPhp en écrivant directement des opcodes au format opcache. C’est le chantier le plus important ; les autres peuvent être réalisés dans l’architecture actuelle.

Aucun de ces changements n’est nécessaire pour les charges de travail d’intégration visées par Phasis.
