Retour à l'accueil

Opcodes PHP : if vs switch vs match — comparaison

L'article compare la structure interne des constructions de branchement en PHP : if/else, switch et match — à travers le prisme des opcodes générés. Il montre comment le compilateur transforme chaque construction en instructions Zend VM, pourquoi switch sur constantes fournit une recherche O(1), comment switch(true) annule l'optimisation, et quand match est préférable en termes de sécurité et de lisibilité.

if/else vs switch vs match : ce qui se passe sous le capot en PHP
Advertisement 728x90

Comment PHP compile les instructions if/else, switch et match : une analyse des opcodes pour les développeurs

Le moteur PHP n’exécute pas directement le code source — il le compile d’abord en une séquence de bytecode (opcodes) que la machine virtuelle Zend exécute. Le choix entre if/else, switch et match influence non seulement la lisibilité, mais aussi la structure de cette séquence : parcours linéaire, recherche par hachage ou retour inline. Pour les développeurs de niveau intermédiaire et senior, comprendre ces différences est essentiel lorsqu’ils conçoivent des logiques de branchement à forte charge, notamment dans les cœurs de frameworks, les routeurs et les logiques métier comportant des dizaines de conditions.

Pourquoi les opcodes ne sont pas une abstraction — ils représentent la performance

Les opcodes sont de véritables instructions que la machine virtuelle Zend traite étape par étape. Leur nombre, leur ordre et leur type déterminent le nombre de cycles CPU, les manques de cache et la précision de la prédiction des branchements. Par exemple, un if/else avec 10 branches effectue jusqu’à 10 comparaisons (IS_IDENTICAL) et jusqu’à 10 sauts conditionnels (JMPZ) dans le pire des cas. Chaque saut peut entraîner une mauvaise prédiction du processeur — surtout avec des données d’entrée irrégulières. En revanche, un switch sur des littéraux de chaîne utilise SWITCH_STRING : une seule opération de hachage et un seul saut inconditionnel (JMP) vers la bonne branche. Cette différence n’est pas théorique — elle se mesure en cycles d’horloge et apparaît dans les microbenchmarks lorsque l’on exécute plus d’un million d’appels par seconde.

Comparaison des trois approches au niveau des instructions

Examinons trois implémentations d’un même algorithme : attribuer une remise en fonction du statut du client ('gold', 'silver', 'bronze', default). Tous les exemples sont exécutés sur PHP 8.1 avec phpdbg -p, sans optimisations OPcache — c’est-à-dire en montrant la sortie « brute » du compilateur.

Google AdInline article slot

if/else : recherche linéaire O(N)

Chaque branche est une paire distincte de comparaison et de saut. Il n’y a aucune optimisation de l’ordre : le compilateur conserve l’ordre original des elseif. Lorsque $status === 'bronze', trois vérifications IS_IDENTICAL, trois sauts JMPZ et un JMP vers l’affectation ont lieu. Dans le pire des cas, on compte N comparaisons et N−1 sauts.

switch : un hybride de table de hachage et de parcours de secours

L’instruction principale est SWITCH_STRING. Pendant la compilation, PHP construit une table de hachage : chaque littéral de chaîne ('gold', 'silver') est mappé à l’adresse de l’instruction cible. À l’exécution, le hachage de $status est calculé, et la VM saute immédiatement vers le bon bloc. La complexité est amortie en O(1). Le bloc de secours (instructions 0002–0008 dans le code original) s’active uniquement en cas de collision de hachage ou de comparaison non stricte — dans les cas d’utilisation typiques, il ne s’exécute jamais.

match : retour inline sans sauts

match est une expression, pas un opérateur. Ses opcodes ne contiennent ni JMP ni JMPZ. Le compilateur génère une séquence de IS_IDENTICAL + JMPNZ uniquement pour la validation, mais le résultat est immédiatement affecté via ASSIGN à la variable cible — sans étiquettes ni transitions intermédiaires. Pour trois branches, cela se traduit par trois comparaisons et trois affectations poussées sur la pile, mais la valeur finale est tirée de la première correspondance =>. Cela rend match plus prévisible et plus sûr sur le plan sémantique (pas de chute), mais il n’est pas plus rapide que switch quand il y a beaucoup de branches.

Google AdInline article slot

Principales limites et pièges

  • switch(true) détruit l’optimisation : avec case $x > 100, le compilateur ne peut pas construire de table de hachage — il génère plutôt une chaîne linéaire de IS_SMALLER + JMPNZ, identique à if/else.
  • Les types clés comptent : SWITCH_LONG fonctionne pour les entiers, tandis que SWITCH_STRING fonctionne pour les chaînes. Les types mixtes ou les objets obligent le moteur à utiliser le mode de secours.
  • break est un JMP : chaque break se compile en un saut inconditionnel (JMP → fin). Pas de break signifie que la VM continue l’exécution dans l’ordre — d’où la chute. Ce n’est pas un « comportement de case » ; c’est un effet secondaire de l’absence de l’instruction.
  • default n’est pas une branche spéciale : c’est simplement une étiquette placée dans les opcodes après toutes les autres cases. Si case 'bronze' n’a pas de break, l’exécution « tombe » sur default non pas à cause de la logique, mais simplement parce que les instructions se suivent les unes après les autres.

Ce qui compte

  • if/else évolue linéairement : avec 50 branches, il y a jusqu’à 50 comparaisons dans le pire des cas.
  • switch sur des chaînes ou des entiers constants offre des recherches en O(1) grâce aux tables de hachage compilées.
  • match est plus sûr et plus propre sur le plan sémantique, mais il n’est pas plus rapide que switch quand il y a plus de 10 branches — il vérifie toujours les valeurs séquentiellement.
  • switch(true) avec des expressions dans les case est un anti-pattern : c’est if/else avec du sucre syntaxique et une surcharge supplémentaire.
  • Les if/else imbriqués augmentent exponentiellement le nombre de sauts : 3 niveaux × 5 branches = jusqu’à 15 JMPZ et 15 JMP.

Pour les professionnels techniques : si votre code contient des chaînes de plus de 7 instructions elseif ou s’il est utilisé dans des chemins critiques (routage, sérialisation, validation), les remplacer par switch avec des clés constantes apporte des gains mesurables en débit. Dans PHP 8.0+, match doit être utilisé là où la sécurité et la lisibilité comptent plus que la vitesse maximale.

L’optimisation doit commencer par l’analyse des opcodes — pas par des suppositions. Exécutez phpdbg -p script.php sur votre section critique et comptez le nombre d’instructions IS_ et JMP. S’il y en a plus de 10, il est temps de refactoriser.

— Editorial Team

Google AdInline article slot
Advertisement 728x90

Lire ensuite