Zpět na domů

Opcodes PHP: if vs switch vs match — srovnání

Článek porovnává vnitřní uspořádání konstrukcí větvění v PHP: if/else, switch a match — přes prizmu generovaných opcodes. Je ukázáno, jak kompilátor převádí každou konstrukci na instrukce Zend VM, proč switch na konstantách zajišťuje O(1) vyhledávání, a switch(true) snižuje optimalizaci na nulu, a kdy je match preferovatelnější z hlediska bezpečnosti a čitelnosti.

if/else vs switch vs match: co se děje pod kapotou v PHP
Advertisement 728x90

Jak PHP kompiluje if/else, switch a match: analýza opcodes pro vývojáře

PHP-motor nevykonává původní kód přímo — nejprve jej kompiluje na sekvenci bajtkódu (opcodes), kterou pak vykonává Zend VM. Volba mezi if/else, switch a match ovlivňuje nejen čitelnost, ale i strukturu této sekvence: lineární procházení, hashování nebo inline návrat. Pro middle/senior vývojáře je pochopení těchto rozdílů klíčové při navrhování vysokonákladových větvících struktur, zejména v jádrech frameworků, routerech a business logice s desítkami podmínek.

Proč opcodes nejsou abstrakce, ale výkon

Opcodes jsou skutečné instrukce, které Zend VM zpracovává krok za krokem. Jejich počet, pořadí a typ určují CPU cykly, cache missy a předvídatelnost větvic. Například if/else s 10 větvemi v nejhorším případě provede 10 porovnání (IS_IDENTICAL) a až 10 podmíněných skoků (JMPZ). Každý takový skok může způsobit mispredikci v procesoru — zejména při nepravidelných vstupních datech. Naopak switch u řetězcových konstant používá SWITCH_STRING: jednu hash operaci a jeden bezpodmínečný skok (JMP) k potřebné větvi. Rozdíl není teoretický — měří se v taktách a projevuje se v mikrobenchmarcích při více než 10⁶ volání za sekundu.

Srovnání tří přístupů na úrovni instrukcí

Vezmeme si tři implementace stejného algoritmu — přiřazení slevy dle statusu klienta ('gold', 'silver', 'bronze', default). Všechny příklady jsou provedeny na PHP 8.1 s phpdbg -p, bez OPcache optimalizací — tedy ukazují „syrový“ výstup kompilátoru.

Google AdInline article slot

if/else: O(N) lineární hledání

Každá větev je samostatná dvojice porovnání a skoku. Není žádná optimalizace pořadí: kompilátor zachovává původní pořadí elseif. Při $status === 'bronze' proběhnou tři IS_IDENTICAL, tři JMPZ a jeden JMP k přiřazení. V nejhorším případě to znamená N porovnání a N−1 skoků.

switch: hybrid hash tabulky a fallback-procházení

Hlavní instrukce je SWITCH_STRING. Při kompilaci PHP vytvoří hash tabulku: každému řetězci ('gold', 'silver') je přiřazen adres a cílové instrukce. Při vykonávání se vypočítá hash $status a VM okamžitě přeskočí k potřebnému bloku. Složitost je amortizovaná O(1). Fallback blok (instrukce 0002–0008 v originále) se aktivuje pouze při kolizi hashu nebo nepravém porovnání — v typickém use-case se neprovede.

match: inline návrat bez skoků

match je výraz, nikoli operátor. Jeho opcodes neobsahují JMP ani JMPZ. Kompilátor generuje sekvenci IS_IDENTICAL + JMPNZ pouze pro kontrolu, ale výsledek je okamžitě přiřazen prostřednictvím ASSIGN do cílové proměnné — bez mezistupňových metek a přechodů. U tří větví to znamená 3 porovnání a 3 přiřazení do stohu, ale konečná hodnota se bere z prvního shodného =>. To dělá match semantičtěji předvídatelnější a bezpečnější (žádné fall-through), ale není rychlejší než switch při velkém počtu větví.

Google AdInline article slot

Klíčová omezení a háčky

  • switch(true) ničí optimalizaci: při case $x > 100 nemůže kompilátor vytvořit hash tabulku — vzniká lineární řetězec IS_SMALLER + JMPNZ, identický s if/else.
  • Typy klíčů jsou důležité: SWITCH_LONG funguje pro celá čísla, SWITCH_STRING pro řetězce. Smíšené typy nebo objekty nutí motor použít fallback.
  • break je JMP: každý break se kompiluje na bezpodmínečný skok (JMP → end). Absence break znamená, že VM bude pokračovat výkonu podle pořadí — odtud fall-through. To není „chování case“, ale vedlejší efekt absence instrukce.
  • default není zvláštní větev: je to jen metka umístěná v opcodes po všech case. Pokud case 'bronze' nemá break, výkon „propadne“ do default ne kvůli logice, ale protože instrukce jdou po sobě.

Co je důležité

  • if/else se škáluje lineárně: při 50 větvích v nejhorším případě to znamená 50 porovnání.
  • switch u konstantních řetězců/celých čísel poskytuje O(1) hledání díky kompilaci hash tabulky.
  • match je bezpečnější a semantičtěji čistší, ale není rychlejší než switch při více než 10 větvích — stále kontrolovalo hodnoty postupně.
  • switch(true) s výrazy v case je antipattern: je to if/else s syntaktickým cukrem a dalším overheadem.
  • Vnořené if/else exponenciálně zvyšují počet skoků: 3 úrovně × 5 větví = až 15 JMPZ a 15 JMP.

Pro technické specialisty: pokud váš kód obsahuje řetězce více než 7 elseif nebo se používá v hot path (routing, serializace, validace), nahrazení if/else switchem s konstantními klíči přináší měřitelný zisk v throughput. V PHP 8.0+ by se match měl používat tam, kde je důležitá bezpečnost a čitelnost, ne maximální rychlost.

Optimalizace by měla začínat analýzou opcodes — ne domněnkami. Spusťte phpdbg -p script.php na svém kritickém úseku a spočítejte počet IS_ a JMP. Pokud jich je více než 10 — je čas refactorovat.

— Editorial Team

Google AdInline article slot
Advertisement 728x90

Číst dál