Programování

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.

12. 10. 2026 · 4 min čtení

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

  1. Definujte cíl. „Stránka objednávek se načte do 300 ms“, ne „ať je to rychlejší“.
  2. Změřte výchozí stav opakovatelně – stejná data, stejné prostředí.
  3. Profilujte a najděte, kde se čas skutečně tráví.
  4. Opravte jedno místo.
  5. 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.