Výkon a profilování kódu – najděte, co je doopravdy pomalé
Neoptimalizujte naslepo. Jak měřit a profilovat Python (cProfile, py-spy), Node.js (--prof, clinic) a databázi (EXPLAIN ANALYZE) a opravit typické úzké hrdlo.
Nejčastější chyba při optimalizaci je optimalizovat to, co pomalé není. Intuice o výkonu bývá mylná – kód, který „vypadá“ neefektivně, často zabere zlomek času, zatímco skutečná brzda je jeden nevinný databázový dotaz ve smyčce. Proto platí: nejdřív měřit, pak měnit.
Postup v pěti krocích
- Definujte cíl. „Stránka objednávek se načte do 300 ms“, ne „ať je to rychlejší“.
- Změřte výchozí stav opakovatelně – stejná data, stejné prostředí.
- Profilujte a najděte, kde se čas skutečně tráví.
- Opravte jedno místo.
- Změřte znovu a porovnejte. Pokud se nic nezlepšilo, změnu vraťte.
Rychlé měření
# Celkový čas příkazu
time python3 report.py
# Opakované měření s porovnáním (nástroj hyperfine)
hyperfine 'python3 report_old.py' 'python3 report_new.py'
V Pythonu pro malé úseky kódu modul timeit:
python3 -m timeit -s "data = list(range(10000))" "sum(x*x for x in data)"
U webu si změřte čas odpovědi:
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s, celkem: %{time_total}s\n" https://example.cz/objednavky
Profilování v Pythonu
cProfile – vestavěný profiler
python3 -m cProfile -s cumtime report.py | head -30
Sloupce, které vás zajímají:
ncalls– kolikrát se funkce zavolala (podezřele vysoká čísla = smyčka, která dělá víc, než má),tottime– čas strávený přímo ve funkci,cumtime– čas včetně volaných funkcí.
Pro vizualizaci uložte výstup a otevřete ho v nástroji snakeviz:
python3 -m cProfile -o profil.out report.py
pip install snakeviz && snakeviz profil.out
py-spy – profilování běžícího procesu
Výhoda py-spy: nepotřebuje měnit kód a lze ho připojit k produkčnímu procesu.
pip install py-spy
py-spy top --pid 12345 # živý přehled jako top
py-spy record -o profil.svg --pid 12345 # flame graph
Připojení k běžícímu procesu obvykle vyžaduje sudo.
Jak číst flame graph
Osa X je podíl času (ne časová osa), osa Y je hloubka zásobníku. Široké bloky = kde se tráví čas. Hledejte široké „plošiny“ nahoře – to jsou funkce, které samy spotřebují nejvíc.
Profilování v Node.js
# Vestavěný V8 profiler
node --prof server.js
# … zatížit aplikaci, ukončit …
node --prof-process isolate-*.log > profil.txt
# CPU profil pro Chrome DevTools
node --cpu-prof app.js # vytvoří soubor .cpuprofile
Soubor .cpuprofile otevřete v Chrome DevTools (záložka Performance) nebo ve VS Code. Případně node --inspect a profilování přímo z chrome://inspect.
Pro analýzu event loopu a vizuální flame grafy je užitečný balíček Clinic.js (npx clinic doctor -- node server.js, npx clinic flame -- node server.js).
Databáze: nejčastější viník
U webových aplikací je úzkým hrdlem velmi často databáze.
N+1 dotazů
# 1 dotaz na objednávky + N dotazů na zákazníky
for order in Order.objects.all():
print(order.customer.name)
# Django: 1 dotaz s JOIN
for order in Order.objects.select_related("customer"):
print(order.customer.name)
Pro vztahy many-to-many slouží prefetch_related. V SQLAlchemy joinedload / selectinload, v Laravelu with().
Jak N+1 odhalit: Django Debug Toolbar, django-silk, nebo prosté zalogování počtu dotazů na request.
EXPLAIN ANALYZE
EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_email = '[email protected]';
Pokud v PostgreSQL vidíte Seq Scan na velké tabulce s filtrem, který vrací pár řádků, chybí index:
CREATE INDEX CONCURRENTLY idx_orders_customer_email ON orders (customer_email);
CONCURRENTLY (PostgreSQL) nezamkne tabulku pro zápis, ale nelze ho spustit uvnitř transakce. V MySQL použijte EXPLAIN (od verze 8.0.18 i EXPLAIN ANALYZE). Pomalé dotazy v PostgreSQL najde rozšíření pg_stat_statements, v MySQL slow query log.
Typické opravy podle nálezu
| Nález v profilu | Obvyklá oprava |
|---|---|
| Tisíce volání DB nebo API | Dávkové načtení, JOIN, cache |
in na velkém seznamu ve smyčce |
Převést seznam na set (O(1) místo O(n)) |
| Opakovaný výpočet se stejným vstupem | functools.lru_cache, memoizace |
Skládání řetězců += ve smyčce |
"".join(casti) |
| Načítání velkého souboru celého do paměti | Čtení po řádcích, generátory, streaming |
| Čekání na síť sekvenčně | Souběžné požadavky (asyncio.gather, Promise.all) |
| Pomalý dotaz | Index, omezit sloupce (SELECT * → potřebné sloupce), stránkování |
Paměť
Pomalost bývá i důsledkem nadměrné spotřeby paměti. V Pythonu tracemalloc (vestavěný) nebo nástroj memray:
pip install memray
memray run report.py
memray flamegraph memray-report.py.*.bin
V Node.js pomůže snímek haldy (heap snapshot) v Chrome DevTools.
Varování na závěr
- Profiler sám zpomaluje program – porovnávejte relativní podíly, ne absolutní časy.
- Měřte s realistickými daty. Dotaz nad 100 řádky na vývojovém stroji se chová jinak než nad 10 miliony v produkci.
- Čitelnost má hodnotu. Optimalizace, která ušetří 2 ms a znečitelní kód, se nevyplatí.
Výstup profileru nebo EXPLAIN ANALYZE můžete vložit do ColdHost AI a nechat si ho vysvětlit – pro složitější analýzu výkonu se hodí model Polaris.