Je rychlejší web v PHP 7 nebo Pythonu 3?

PHP přineslo ve své sedmé major verzi velmi zásadní zrychlení a snížení spotřeby paměti. Podle některých benchmarků je nyní PHP při řešení některých úloh o dost rychlejší než Python 3. Jak je na tom PHP ve své domovské oblasti, tedy na webu? Je i tady výhodnější z pohledu výkonu použít PHP?

Metodika testu

Nebudeme měřit jen Hello world, pro náš malý test si vytvoříme "microservice", která přijme číslo a vrátí odpovídající prvek ve Fibonacciho posloupnosti. Nebudeme stavět na zelené louce, využijeme populární frameworky ze světa PHP i Pythonu.

Díky tomu prověříme více vlastností platformy najednou:

  • rychlost startu aplikace
  • rychlost frameworku
  • hrubý výpočetní výkon jazyka - rychlost rekurzivního volání a sčítání
  • rychlost renderování šablon

Pro všechny testované aplikace si pustíme v jednom procesu Nginx, který bude požadavky předávat backendu přes FastCGI a WSGI. PHP aplikace poběží jako PHP-FPM ve čtyřech procesech. Python aplikace poběží jako UWSGI také ve čtyřech procesech.

Zátěž budeme generovat pomocí nástroje Apache Bench. Pro každý testovaný případ pošleme 100 HTTP dotazů, v 10 souběžných spojeních.

Výsledky

Konec řečí, ukaž čísla!

První číslo Fibonacciho řady je PHP s Nette schopno naservírovat 600x za sekundu. Slim, který je trochu minimalističtější než Nette, pak téměř 2x tolik.

Python 3 s Flaskem tady ale PHP jasně válcují a stihnou vrátit výsledků víc než dvakrát tolik co Slim. Je to pravděpodobně proto, že UWSGI startuje aplikaci pouze jednou na začátku (vytvoří objekt application) a při dalších požadavcích už jen volá její metody. Naproti tomu FastCGI vytváří aplikaci při každém požadavku znovu.

U 18. čísla Fibonacciho řady se situace začíná pomalu obracet. Slim i Flask zvládají vyřídit tisíc požadavků. Vývojářům PHP se zrychlení opravdu podařilo, a tak čím víc aplikace počítá, tím se PHP posouvá před Python.

U 30. čísla je rozdíl už opravdu markantní, PHP upočítá 23 dotazů, zatímco Python pouze 4.

Měli bychom tedy ten pomalý šrot Python hodit z okna? Noo... ještě bych s tím počkal.

Není Python jako Python

Python totiž ještě neřekl poslední slovo :)

Referenční implementace Pythonu (CPython), kterou jsme použili v testu, totiž zdaleka není všechno, co můžeme ve světe Pythonu použít.

Velmi zajímavé je PyPy, které používá JIT kompiler a je téměr drop-in náhradou za CPython. PyPy s Flaskem zvládne vrátit 60 požadavků za sekundu, tedy skoro trojnásobek toho co PHP a Slim. A to se teprve zahříváme.

Další možností je části Python aplikace náročné na výpočet přepsat do Cčka a načíst je jako binární modul. Proč ale přepisovat, když je tu Cython, který to udělá za nás? Napíšeme tedy téměř běžný Python kód, kam pouze doplníme typové informace a označíme, co se bude volat z Pythonu. Cython tento zdrojový kód převede do Cčka a zkompiluje do binárky, kterou můžeme importovat úplně stejným způsobem jako jakýkoliv běžný python modul.

A výsledek? Přes 400 požadavků za sekundu pro 30. číslo posloupnosti, tedy téměř 20x tolik co PHP.

Závěr

Zrychlení PHP ve verzi 7 se opravdu povedlo a pro celou řadu operací strčí referenční CPython do kapsy. Svět Pythonu je ale velmi široký, a tak díky jeho mnoha projektům věnujícím se rychlosti (PyPy, Pyston, Nuitka, Cython) dokáže v případě potřeby PHP dohnat a nakonec nechat daleko za sebou.

Zdrojové soubory benchmarku jsou dostupné na githubu.

Aktualizace 2.1.2021

Po 4 letech jsem zkusil benchmarky zaktualizovat a znovu spustit (ale už na novějším stroji). Jaký je výsledek?

PHP si oproti roku 2017 polepšilo 3-4x, čistý Python je stále pomalejší než PHP, ale už jen 4x (byl 5x).

Náskok Pypy a Cythonu se ale také trochu snížil. Cython byl rychlejší 18x než PHP, nyní už je "jen" 14x.

Hodnocení

Komentáře

2017-02-22 10:21:25

Nejsem si jist jak PHP (myslím že taky ne), ale Python nemá tail-recursion optimalizaci a ani se neplánuje. Tím trpí u těch vyšších hodnot. Pokud člověk píše v Pythonu a chce výkon, takovému kódu se vyhne. Takže by bylo zajímavější udělat test s něčím, co se pak skutečně používá. :-)

Na tento komentář odpověděl [3] Dundee
[2] Václav Havel
2017-02-22 10:50:58

Testovat se musí umět. Obávám se, že jen naprosté minimum webových aplikací potřebuje hrubou výpočetní sílu v integer počtech. Takže se vlastně testovalo něco, co ve webových aplikacích nemá téměř žádné uplatnění.

Je to něco jako kdybyste testoval rychlost auta podle toho, ve kterém modelu auta lze rychleji zašroubovat víčko od nádrže - určitě to bude velice určující pro to, které auto si vybrat.

Na tento komentář odpověděl [3] Dundee
[3] Dundee
2017-02-22 13:12:40

#1 Michal Hořejšek: #2 Václav Havel: Ta rekurze je tam naschvál, je to snaha o simulaci určité práce v aplikaci. Mohl bych tam samozřejmě přidat práci s databází, ale tím by to přestalo být testem jazyka.

Aby se nejednalo jen o test hrubé výpočetní síly, tak se ptáme postupně na jednotlivá čísla posloupnosti. Pro první čísla je výpočet posloupnosti úplně zanedbatelný a měříme tak vlastně jen rychlost startupu, frameworku a renderování šablon. S rostoucím číslem se čím dál více testuje rychlost volání funkcí v jazyce.

Zkusil jsem schválně ten algoritmus přepsat na iterativní a výsledek je: PHP okolo 200 dotazů/s, Python okolo 3000 dotazů/s. Tady zase imho měříme jen startup aplikace (FCGI vs WSGI), takže to také moc komplexní test není.

[4] karl
2017-02-22 21:09:33

Byla u php zapnutá OpCache s nastavením opcache.validate_timestamps = Off?

Na tento komentář odpověděl [8] Dundee
[5] Václav Havel
2017-02-23 00:52:24

Standardní Python je IMHO nejpomalejší interpretr programovacího jazyka široko daleko ze všech programovacích jazyků co znám (a znám jich kolem stovky). To, že je schopen přesto rychlostně soupeřit s PHP říká něco nelichotivého o PHP.

PHP je neefektivně řešeno tak, že každý request musí znovu vybudovat veškerý kontext včetně objektů a dalšího znovu od nuly. Dokonce pokaždé request znovu překládá zdrojový kód do p-kódu. Drobné výjimky pomiňme, protože nestojí prakticky za řeč. To je tak velký overhead při requestu, že nikdo soudný nebude dělat zatěžovanější weby přímo v PHP. (Pokud nepoužije třeba konvertor PHP -> C++ ála facebook - zde ale výsledek už neběží v PHP runtime, a jsme zcela jinde.)

Autoři PHP záměrně vykastrovali PHP proto, aby mohli bokem přivydělávat na nástrojích, které mají jiné jazyky ve vínku a zdarma.

Normální princip webu, který dělá Python, Java, a další přirozeně je nebudovat všechno znovu od nuly při každém requestu. Zdrojáky se nekompilují při každém requestu, řada objektů žije a uchovává se i mezi requesty, různé requesty sdílejí určitou sadu objektů a proměnných, a mnohé další. Tím je cena za request mnohem levnější než u PHP.

PHP má také řadu nectností dalších: Například mizernou práci s řetězci, dokonce tak mizernou, že nemá žádný základní datový typ pro řetězec. To, co PHP nazývá "string" je jen sekvence holých bajtů. Práci s řetězci si pak můžete nasimulovat pomocí knihoven. Porovnejte s luxusní prací s unicode řetězci přímo v Pythonu. Programovací jazyk bez řetězců - to je muzeální vykopávka patřící do doby ENIACu.

Každý webový projekt, který si zvolil PHP a trochu se rozrostl, řeší brutální výkonové problémy. Taková wikipedie si de facto totálně překopala zdrojáky PHP a šíleně je zoptimalizovala, a i tak má několikanásobek serverů, než by na stejné zatížení potřeboval stejný projekt v jiném jazyce. Facebook nakonec naprogramoval kompilátor PHP -> C++, jinak by se asi Zuckenberg už dávno oběsil.

Je naprosto k ničemu, že PHP optimalizuje výkon základní integer aritmetiky, když to celé zabije příšernou cenou za request a dalšími neefektivitami.

Kdybyste porovnával PHP s něčím jiným, než nejpomalejším mainstreamovým jazykem na světě Pythonem - PHP by dostalo daleko více na zadek. Takový web třeba v Javě by natřískal PHP a nechal ho daleko za sebou nejen v rychlosti requestu, ale i v té integer aritmetice.

Zkrátka dokud PHP bude držet šílenou cenu za request, na výkon to nebude. Vzhledem k tomu, že PHP tu je už velice dlouho, a navíc už ve verzi 7 - tak se nejspíše nic nezlepší ani příštích 30 let v PHP. Šílený overhead při request strašně žere výkon i paměť, několikanásobně více, než jakékoli jiné webové řešení. Protože většina webových requestů je light, tedy mnoho toho nedělá, max. vyzvedne data z optimalizované databáze a nějak je zobrazí, pak cena za request je určující pro výkon celého webového sídla.

Na tento komentář odpověděl [6] Jakub Vrána
Na tento komentář odpověděl [7] hmm
2017-02-23 09:16:29

Byla u PHP zapnutá opcode cache? Dělat jakékoliv benchmarky bez toho je nesmysl, protože komu jde o výkon, tak si to zapne.

#5 Václav Havel: Facebook už kompilaci z PHP do C++ už dávno nedělá. Používá teď HHVM, což je JIT kompilátor.

Na tento komentář odpověděl [8] Dundee
[7] hmm
2017-02-23 15:33:06

#5 Václav Havel: php má opcode cache jako standardní součást už asi 6 let.

[8] Dundee
2017-02-24 14:17:17

#4 karl: #6 Jakub Vrána: Ano, opcode cache byla zapnutá.

Zkusil jsem nastavení opcache.validate_timestamps = Off, ale velké zrychlení to nepřineslo, pouze jednotky dotazů navíc.

Komentáře již nelze přidávat