Profilování PHP aplikací pomocí Blackfire

O tom, že je rychlost aplikace důležitá, asi není potřeba diskutovat. Zrychlit aplikaci už tak snadné není. V článku vám předvedu, jak s pomocí Blackfire snadno odhalíte slabá místa vaší PHP aplikace.

Dříve, než začnete zkoumat Blackfire, je dobré zkontrolovat a vyřešit potenciální problémy, které mohou aplikaci brzdit.

Než se pustíte do Blackfire

  • Aktuální verze PHP – doporučuji upgrade alespoň na PHP 7.0, tím zrychlíte aplikace nejvíce a pravděpodobně i nejsnadněji.
  • PHP OPcache – dalším krokem by mělo být nastavení OPcache, čímž aplikaci také dost pomůžete.
  • Optimalizace SQL dotazů – pokud na vás v Debug Baru v Symfony nebo Nette svítí něco „380 SQL queries, 1950 ms“, tak bych začal tam.
  • Frontend – optimalizace frontendu je na samostatný článek, možná spíš knihu. Stručně řečeno je zbytečné se honit za milisekundami v PHP requestu, pokud frontend stahuje megabajty obrázků nebo JavaScriptu s chybějícím cachováním.

Úvod do profilování

Základem je vědět, co v rámci skriptu nebo požadavku zabírá nejvíce času a na to se zaměřit. Je sice hezké trojnásobně zrychlit něco, co trvá 1 % požadavku, ale mnohem efektivnější je trošku zoptimalizovat kód, který trvá 60 %.

O chování aplikace se můžeme něco dozvědět přes var_dump();die();, ale mnohem pohodlnější a efektivnější to je pomocí testů nebo krokování v debuggeru. Obdobně se můžeme dozvědět něco o rychlosti aplikace pomocí:

$start = microtime(true);
someHardStuff();
echo microtime(true) - $start;

Ale mnohem pohodlnější je použít nějaký nástroj. Dříve jsem několikrát použil Xdebug a vizualizaci pomocí WinCacheGrind. Nebylo to špatné, ale pohodlné také ne.

Nedávno jsem narazil na Blackfire, za kterým stojí SensioLabs (stejně jako za Symfony). Je to sice placený nástroj, ale mají verzi zdarma, která na běžné profilování stačí. Ve zbytku článku vám předvedu, jak na něj.

Komponenty Blackfire

Blackfire stack se skládá z několika spolupracujících komponent:
(záměrně se držím oficiálních názvů, abych vás nezmátl, až budete koukat do oficiální dokumentace)

  • Probe je PHP rozšíření, která sleduje běh aplikace a sbírá informace o tom, co jak dlouho trvalo
  • Agent je aplikace, která zpracovává data z Probe, anonymizuje je a agreguje. Následně je odesílá do aplikace na blackfire.io
  • Companion je rozšíření do prohlížeče, kterým spustíte profilování otevřené stránky
  • Client je CLI aplikace pro spouštění profilování z příkazové řádky
  • Blackfire.io je webová aplikace, která zobrazuje naměřená data

V následující části vás provedu samotnou instalací jednotlivých částí.

Instalace Blackfire

Schválně tu neuvádím detailní popis instalace, protože by rychle zastaral. Aktuální verzi vždy najdete na blackfire.io.

  1. Pro používání Blackfire je potřeba si vytvořit účet na Blackfire.io. Můžete využít přihlášení GitHubem, Google účtem nebo SensioLabsConnect („Symfony účet“).
  2. Stáhněte si Blackfire Agent a Client podle návodu pro váš systém. V mém případě (Windows) to znamenalo stažení .zip balíčku s blackfire.exe a blackfire-agent.exe a jejich rozbalení do C:\dev\blackfire\. Následně jsem složku přidal do systémové cesty.
  3. Spusťte příkaz blackfire config a zadejte postupně Client ID a Client Token z dokumentace (Docela hezká UX vychytávka je, že po přihlášení ukazují v dokumentaci přímo vaše tokeny).
  4. Spusťte blackfire-agent -register zadejte vaše Server ID a Server Token z dokumentace.
  5. do Chrome si nainstalujte rozšíření pro snadné profilování webů
  6. Stáhněte si správnou verzi extension do PHP, přesuňte ji do adresáře php\ext a do php.ini přidejte:
[blackfire]
extension=php_blackfire.dll

Po zavolání php -v by se Blackfire rozšíření mělo ukázat ve výpisu

λ php -v
PHP 7.1.3 (cli) (built: Mar 14 2017 23:46:25) ( NTS MSVC14 (Visual C++ 2015) x86 )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2017 Zend Technologies
    with Zend OPcache v7.1.3, Copyright (c) 1999-2017, by Zend Technologies
    with blackfire v1.16.0~win-x32-non_zts71, https://blackfire.io, by Blackfireio Inc.

Jdeme profilovat

V Google Chrome si otevřete stránku, kterou chcete profilovat, a spusťte profilování pomocí Blackfire ikony.

Spuštění měření z Google Chrome

Následně se spustí běh profileru a po doběhnutí se zobrazí tlačítko View Call Graph. To otevře výsledek měření na webu blackfire.io.

Zobrazení výsledku měření na Blackfire.io

Vlevo je vidět seznam volaných funkcí a vpravo graf jejich volání. Naším cílem je najít část aplikace, která trvá zbytečně dlouho nebo se vykonává podezřele hodně krát.

Poznámka pro Symfony (a možná i jiné frameworky): Pro profilování je vhodné aplikaci spouštět v produkčním režimu, protože informace, že 8,37 % doby běhu trvalo sbírání dat pro Symfony Debugger nám moc nepomůže. Naopak v datech pak nemusí být vidět jiné dlouhotrvající činnosti.

Takže pokud v případně Symfony Demo application budeme prozkoumávat graf volání odshora, nalezneme například toto:

Z požadavku zabere 20 % času jen převod Markdownu do HTML

To znamená, že renderování šablony zabírá 40 % času požadavku. Část z toho zabírá stránkovadlo (dělá dotazy do databáze), ale 20 % (z celkového času požadavku!) zabírá převod Markdown do HTML. Je tam totiž výpis 10 článků, takže se volá 10×. Volá se úplně zbytečně – stačilo by ho zavolat jen jednou při změně článku a vyrenderované HTML uložit do databáze. Podobně se chová i detail článku, kde se pro změnu renderují komentáře.
(V tomhle případě jde o demo aplikaci, takže předpokládám, že ta Twig Extension tam má hlavně výukový důvod).

Optimalizujeme

Pokud před profilováním vyberete z nabídky Create new reference, uloží se dané měření jako referenční. To znamená, že proti němu pak snadno můžete porovnávat další měření. Obecně práce s referencemi funguje takto – provedete měření pro výchozí stav (třeba master branch) a to označíte jako referenční. Následně zkoušíte optimalizovat a provádíte další měření, které porovnáváte proti tomu referenčnímu.

Po zoptimalizování aplikace (jen jsem ze šablon odebral |md2html a předstíral jsem, že z databáze načítám už hotové HTML), jsem profilování spustil znovu. Tentokrát jsem zvolil, aby se výsledky porovnaly s předtím udělaným referenčním měřením.

Ze screenshotu je vidět, že se aplikace opravdu zrychlila. Nejsou ani tak důležitá absolutní čísla (měření času může být ovlivněno vnějšími vlivy a chováním systému), ale to, že se už nevolá markdownToHtml.

Po zoptimalizování

Ještě se vrátím k těm externím vlivům. Když jsem totiž spouštěl měření po optimalizaci, tak první dvě měření vrátily horší výsledek než před optimalizací. Ale z grafu bylo vidět, že se zpomalily filesystémové operace. Takže se měření asi potkalo s něčím dalším (další písnička na Spotify, systémové indexování pro vyhledávání nebo něco takového). Blackfire se to snaží řešit tím, že si při měření stránku načte vícekrát a vybere nejreprezentativnější výsledek (u mě to nezafungovalo pravděpodobně proto, že ty věci na pozadí trvaly déle).

Profilování v CLI

Když jsem při profilování naší aplikace narazil na to, že velkou část trvání požadavku zabírá deserializace UUID pomocí ramsey/uuid, tak jsem svou optimalizační snahu namířil dvěma směry. Nejdříve jsem zoptimalizoval Doctrine DQL, aby nejoinovalo polovinu světa (a tedy nepotřebovalo hydratovat tolik UUID). A potom jsem se podíval na samotnou knihovnu ramsey/uuid.

Díky tomu, že deserializace UUID je věc, ke které není potřeba zbytek Symfony aplikace, vytvořil jsem si na to jednoduchý benchmark. Jeden skript pro nagenerování sady testovacích UUID a druhý pro samotné profilování. Těch UUID jsem si na testování nageneroval hodně (200 tisíc), protože ta knihovna je přeci jen dost rychlá a na jednom by zrychlení nebylo tak dobře vidět.

Následně jsem mohl spustit měření pomocí:

λ blackfire run php mh\benchmark.php

V tomhle případě by Blackfire udělal jen jedno měření, takže je nutné přidat --samples=10 (v Chrome to dělá automaticky):

λ blackfire --samples=10 run php mh\benchmark.php

A protože šlo o měření výchozího stavu, přidal jsem do příkazu ještě --new-reference, aby se měření uložilo jako referenční a mohl jsem další měření snadno porovnávat proti němu. Celý příkaz tedy vypadal takto:

λ blackfire --samples=10 --new-reference run php mh\benchmark.php

Výstupem bylo:

Blackfire Run completed
Graph URL https://blackfire.io/profiles/55b49ce9-5318-4553-bbd4-8415464ecff2/graph
Reference: #1. Untitled
No tests! Create some now https://blackfire.io/docs/cookbooks/tests
No recommendations

Wall Time 6.14s
CPU Time n/a
I/O Time n/a
Memory 50.5MB
Network n/a n/a n/a
SQL n/a n/a

Ve výstupu si všimněte, že se měření označilo jako reference 1 (Reference: #1. Untitled), budeme ho potřebovat v následujícím kroku.

Potom jsem zkusil kód optimalizovat a znovu spustil měření. Tentokrát jsem --new-reference nahradil za --reference=1, aby se výsledky porovnaly s referenčním měřením před optimalizací.

λ blackfire --samples=10 --reference=1 run php mh\benchmark.php

Blackfire Run completed
Graph URL https://blackfire.io/profiles/compare/55b49ce9-5318-4553-bbd4-8415464ecff2...e23be653-07ec-48d4-b15a-32d62ef542d6/graph
No tests! Create some now https://blackfire.io/docs/cookbooks/tests
No recommendations

Wall Time 5.64s -506ms -9.9%
CPU Time n/a n/a
I/O Time n/a n/a
Memory 50.5MB none
Network n/a n/a n/a
SQL n/a n/a

Z výsledků je vidět, že po optimalizaci kód proběhl rychleji. Nicméně to nestačí, protože to může být způsobené chybou měření. Je potřeba ve webovém rozhraní ověřit, že zrychlení se týká jen té části kódu, kterou jsme optimalizovali – což je vidět na následujícím obrázku:

Zoptimalizované UUID

Posledním krokem bylo poslání Pull Requestu. Určitě není od věci v takovém případě přiložit i benchmark, aby si zrychlení mohl ověřit i někdo další (třeba na jiném systému).

Kolik Blackfire stojí?

Na vše, co jsem v článku ukazoval, stačí free verze Blackfire. V placené umí navíc profilovat HTTP požadavky a SQL dotazy (bez těch se obejdu, protože jsou dobře vidět v Symfony Debug Baru). Kromě toho by měl umět automatizované testování výkonu (třeba v rámci CI), ale to jsem nezkoušel.

Druhá věc je, že obecně nemám rád závislost na službách třetích stran – nikdy nevíte, kdy je někdo koupí jako acqui-hire a nástroj vypnou. Takže bych podobnou službu nechtěl používat třeba na deploy. Ale tady co si zoptimalizujete, už vám nikdo nevezme.

Pokud se o fungování Blackfire chcete dozvědět více, tak doporučuji seriál článků 24 Days of Blackfire

Už profilujete?

Zkoušeli jste už profilovat? Používáte Blackfire nebo něco jiného? Budu rád, pokud se o vaše tipy podělíte v komentářích. A úplně nejradši budu, pokud se vám díky článku podaří odhalit a vyřešit nějaký výkonnostní problém.

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 8

Přehled komentářů

Lamicz
Oldis Re:
risototh Re:
Martin Hujer Re:
petr
V.K.
Michal Prynych phpstorm
Tintin
Zdroj: https://www.zdrojak.cz/?p=19805