perf — profilowanie CPU i analiza wydajności na Linux
Opublikowano: 10 kwietnia 2026 · Kategoria: VPS
Serwer zjada 95% CPU, ale logi aplikacji wyglądają normalnie. Która funkcja powoduje
bottleneck? Czy problem to brak danych w cache procesora, błędne przewidywanie skoków
warunkowych, czy może wolne I/O? perf — narzędzie z jądra Linux — odpowiada na te
pytania korzystając z hardware performance counters procesora, bez potrzeby modyfikacji kodu.
Google i Netflix używają go codziennie do optymalizacji produkcyjnych systemów.
Instalacja i perf stat — szybki przegląd wydajności
# Ubuntu/Debian — zainstaluj linux-tools pasujące do kernela sudo apt install linux-tools-$(uname -r) linux-tools-common -y perf --version # perf stat — podstawowe statystyki CPU perf stat ls /usr # Output: # Performance counter stats for 'ls /usr': # # 1.234567 task-clock (msec) # 0.456 CPUs utilized # 2 context-switches # 0.001 M/sec # 0 cpu-migrations # 0.000 M/sec # 123 page-faults # 0.100 M/sec # 3,456,789 cycles # 2.800 GHz # 2,890,123 instructions # 0.84 insns per cycle <--- IPC! # 567,890 branches # 460.238 M/sec # 23,456 branch-misses # 4.13% of all branches # # Statystyki dla działającego procesu (-p PID) perf stat -p $(pgrep php-fpm | head -1) sleep 10 # Własny zestaw liczników perf stat -e cycles,instructions,cache-misses,cache-references,LLC-loads,LLC-load-misses \ python3 benchmark.py
perf top — interaktywny profiler w czasie rzeczywistym
perf top działa jak htop ale na poziomie funkcji — pokazuje które funkcje w całym
systemie lub konkretnym procesie zużywają najwięcej cykli CPU. Odświeża się co kilka sekund.
# Systemowy top funkcji (wymaga root) sudo perf top # Tylko dla konkretnego procesu sudo perf top -p $(pgrep node | head -1) # Przykładowy output: # Samples: 4K of event 'cycles', 4000 Hz, Event count (approx.): 1234567890 # Overhead Shared Object Symbol # -------- ---------------- ------------------------- # 45.32% [kernel] [k] schedule # 23.18% libc.so.6 [.] malloc # 15.44% myapp [.] process_request # 8.12% libssl.so.3 [.] AES_encrypt # 4.23% [kernel] [k] tcp_sendmsg # Przydatne klawisz w trybie interaktywnym: # 'a' - annotate (pokaż asm z hot path) # 's' - sort by overhead # 'f' - filter by name # 'z' - zero counters (reset)
perf record i perf report — szczegółowa analiza
# Nagraj próbki z call graph (stack traces) sudo perf record -g -p $(pgrep nginx | head -1) -- sleep 30 # Wynikowy plik: perf.data # Analizuj interaktywnie (TUI) sudo perf report -g # Analizuj bez interakcji (dump do stdout) sudo perf report --stdio | head -100 # Nagrywaj konkretne zdarzenie (LLC misses) sudo perf record -e LLC-load-misses:u -g -p <PID> -- sleep 15 sudo perf report -g fractal # Nagrywaj cały system przez N sekund sudo perf record -a -g -F 99 -- sleep 30 # -a = all CPUs, -F 99 = 99 Hz sampling (bezpieczne dla prod) # Nagrywaj tylko user-space (bez kernel frames) sudo perf record -u -g ./myapp
FlameGraph — wizualizacja wyników
Raporty tekstowe perf są trudne do analizy przy głębokich call graphs. FlameGraph Brendana Gregga zamienia je w interaktywne wykresy SVG — szerokie kolumny to gorące ścieżki kodu.
# Pobierz FlameGraph scripts git clone https://github.com/brendangregg/FlameGraph.git /opt/flamegraph # Nagraj dane z call graph sudo perf record -a -g -F 99 -- sleep 30 # Generuj FlameGraph SVG sudo perf script | \ /opt/flamegraph/stackcollapse-perf.pl | \ /opt/flamegraph/flamegraph.pl > flamegraph.svg # Otwórz w przeglądarce (lub pobierz przez scp) python3 -m http.server 8888 # i wejdź na http://server-ip:8888/flamegraph.svg # Filtruj tylko określone symbole sudo perf script | \ /opt/flamegraph/stackcollapse-perf.pl | \ grep -v "^kernel" | \ /opt/flamegraph/flamegraph.pl \ --title "PHP-FPM CPU Profile" \ --width 1400 > php-profile.svg # Różnicowy FlameGraph (before vs after optymalizacji) # 1. Nagraj before.data i after.data # 2. perf script -i before.data | stackcollapse-perf.pl > before.txt # 3. perf script -i after.data | stackcollapse-perf.pl > after.txt # 4. difffolded.pl before.txt after.txt | flamegraph.pl > diff.svg
Hardware counters — LLC misses i branch mispredictions
| Counter | Dobra wartość | Problem gdy | Przyczyna / naprawa |
|---|---|---|---|
| IPC (insns/cycle) | > 2.0 | < 1.0 | Cache misses, branch mispredictions, memory-bound app |
| LLC-load-misses | < 1% LLC-loads | > 5% | Zła lokalność danych — przepisz struktury danych (SoA zamiast AoS) |
| branch-misses | < 1% | > 3% | Unpredictable branches — sortuj dane przed pętlą, użyj branchless patterns |
| cache-misses | < 0.1% | > 1% | Brak lokalności L1/L2 — prefetching, zmiana algorytmu |
| context-switches | Niskie | Tysiące/sek | Za dużo wątków względem CPU, spinlocki, I/O bound threads |
Profilowanie PHP-FPM i Node.js
# PHP-FPM — wymaga PHP skompilowanego z --enable-frame-pointers # Sprawdź czy jest wsparcie: php -i | grep "frame pointers" # lub sprawdź: readelf -s /usr/lib/php/... | grep frame # Nagraj próbki dla master PHP-FPM sudo perf record -g -F 99 -p $(pgrep -x php-fpm8.2 | head -1) -- sleep 30 sudo perf report --stdio | head -50 # Node.js — z mapowaniem JIT symbols node --perf-basic-prof app.js & NODE_PID=$! sudo perf record -g -F 99 -p $NODE_PID -- sleep 30 # Zmerguj Node JIT map z perf symbols sudo perf script --symfs /tmp | \ /opt/flamegraph/stackcollapse-perf.pl | \ /opt/flamegraph/flamegraph.pl > node-flame.svg # Java — użyj async-profiler (nie wymaga frame pointers) # wget https://github.com/async-profiler/async-profiler/releases/... java -agentpath:/opt/async-profiler/lib/libasyncProfiler.so=start,event=cpu,file=profile.html app.jar
Porównanie narzędzi profilowania
| Narzędzie | Metoda | Narzut | Kiedy używać |
|---|---|---|---|
| perf | Sampling (PMU) | Niski (1-3%) | Produkcja, CPU profiling, cache analysis |
| gprof | Instrumentacja | Wysoki (20-50%) | Dev/test, wymaga recompile z -pg |
| Valgrind Callgrind | Simulacja CPU | Bardzo wysoki (10-50x) | Dokładny cache analysis, tylko dev/test |
| async-profiler | Sampling JVMTI | Niski | Java/Kotlin produkcja, bez safepoint bias |
| Py-Spy | Sampling Python | Minimalny | Python produkcja, FlameGraph output |
| rbspy | Sampling Ruby | Minimalny | Ruby on Rails produkcja |