Dlaczego profilowanie jest ważne?
Profilowanie to proces zbierania statystyk o wykonaniu programu — które funkcje są wywoływane najczęściej, ile czasu zajmują, ile pamięci zużywają. Korzyści:
- 🎯 Identyfikacja problemów – znajdowanie rzeczywistych wąskich gardeł
- 📊 Mierzenie postępów – weryfikacja, czy optymalizacje działają
- 💡 Świadome decyzje – optymalizujesz tylko to, co naprawdę trzeba
- 🚀 Lepsza wydajność – aplikacje działają szybciej i zużywają mniej zasobów
Zasada: "Measure, don't guess" — zawsze mierz wydajność przed i po optymalizacji.
Jeśli chcesz poznać ogólne techniki optymalizacji, sprawdź Zaawansowane techniki w Pythonie.
timeit – mierzenie czasu wykonania
timeit to najprostsze narzędzie do mierzenia czasu wykonania małych fragmentów kodu.
Podstawowe użycie
Python1 2 3 4 5import timeit # Mierzenie czasu wykonania pojedynczej operacji czas = timeit.timeit('sum(range(1000))', number=10000) print(f"Czas: {czas:.4f} sekundy dla 10000 wywołań")
timeit w Jupyter Notebook
W Jupyter można użyć magic command:
Python1 2%%timeit sum(range(1000))
timeit.Timer dla bardziej złożonych przykładów
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20import timeit # Porównanie dwóch implementacji setup = """ def funkcja_a(): return sum(range(1000)) def funkcja_b(): total = 0 for i in range(1000): total += i return total """ czas_a = timeit.timeit('funkcja_a()', setup=setup, number=10000) czas_b = timeit.timeit('funkcja_b()', setup=setup, number=10000) print(f"Funkcja A: {czas_a:.4f}s") print(f"Funkcja B: {czas_b:.4f}s") print(f"Różnica: {(czas_b/czas_a - 1)*100:.1f}%")
Zalety timeit
- ✅ Automatyczne uruchamianie wiele razy dla dokładności
- ✅ Automatyczne wyłączenie garbage collectora dla lepszej precyzji
- ✅ Łatwe porównywanie różnych implementacji
Wady timeit
- ❌ Tylko całkowity czas wykonania
- ❌ Nie pokazuje, gdzie kod spędza czas
- ❌ Nie nadaje się do analizy całych aplikacji
cProfile – statystyczne profilowanie
cProfile to standardowe narzędzie do profilowania całych programów Python. Pokazuje, które funkcje są wywoływane, ile razy i ile czasu zajmują.
Użycie z linii poleceń
Bash1python -m cProfile -o output.prof script.py
Plik output.prof można analizować za pomocą pstats:
Python1 2 3 4 5import pstats stats = pstats.Stats('output.prof') stats.sort_stats('cumulative')# Sortuj według czasu kumulacyjnego stats.print_stats(20)# Pokaż top 20 funkcji
Użycie w kodzie
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24import cProfile import pstats def moja_funkcja(): # Twój kod do profilowania total = 0 for i in range(10000): total += i * 2 return total # Profilowanie profiler = cProfile.Profile() profiler.enable() # Wykonaj kod for _ in range(100): moja_funkcja() profiler.disable() # Analiza wyników stats = pstats.Stats(profiler) stats.sort_stats('cumulative') stats.print_stats(20)
Interpretacja wyników cProfile
Typowy output cProfile:
Bash1 2 3 4 5 6 7 8100006 function calls in 0.250 seconds Ordered by: cumulative time ncallstottimepercallcumtimepercall filename:lineno(function) 10.0000.0000.2500.250 script.py:1(<module>) 1000.1500.0020.2500.003 script.py:3(moja_funkcja) 1000000.1000.0000.1000.000 {built-in method range}
- ncalls – liczba wywołań funkcji
- tottime – całkowity czas spędzony w funkcji (bez funkcji wywoływanych)
- percall – średni czas na wywołanie (tottime/ncalls)
- cumtime – czas kumulacyjny (włączając funkcje wywoływane)
- filename:lineno(function) – lokalizacja funkcji
SnakeViz – wizualizacja wyników
SnakeViz to narzędzie do wizualizacji profilów cProfile:
Bash1 2pip install snakeviz snakeviz output.prof
Otworzy interaktywną wizualizację w przeglądarce.
line_profiler – profilowanie linia po linii
line_profiler pokazuje, ile czasu zajmuje każda linia kodu. To kluczowe narzędzie do znajdowania wąskich gardeł.
Instalacja line_profiler
Bash1pip install line_profiler
Użycie z dekoratorem
Python1 2 3 4 5 6 7 8 9 10 11 12@profile def przetwarzaj_dane(data): wynik = [] for item in data: # Linia 1 if item > 0: # Linia 2 wynik.append(item * 2) # Linia 3 else: # Linia 4 wynik.append(0) # Linia 5 return wynik # Linia 6 # Uruchom profilowanie: # kernprof -l -v script.py
Uruchomienie
Bash1kernprof -l -v script.py
Output pokazuje czas dla każdej linii:
Line #Hits TimePer Hit % TimeLine Contents
==============================================================
1 @profile
2 def przetwarzaj_dane(data):
3 122.00.0wynik = []
4 10000 50000.5 50.0for item in data:
58000 30000.4 30.0if item > 0:
68000 20000.3 20.0wynik.append(item * 2)
720005000.35.0else:
820003000.23.0wynik.append(0)
9 111.00.0return wynik
line_profiler programowo
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17from line_profiler import LineProfiler def moja_funkcja(data): wynik = [] for item in data: wynik.append(item * 2) return wynik profiler = LineProfiler() profiler.add_function(moja_funkcja) profiler.enable() data = list(range(10000)) moja_funkcja(data) profiler.disable() profiler.print_stats()
memory_profiler – profilowanie pamięci
memory_profiler pokazuje zużycie pamięci linia po linii. To szczególnie przydatne do znajdowania problemów z alokacją pamięci. Jeśli chcesz zrozumieć, jak Python zarządza pamięcią, sprawdź Zaawansowane zarządzanie pamięcią i garbage collector.
Instalacja memory_profiler
Bash1 2pip install memory-profiler pip install psutil# Dla lepszej precyzji
Użycie memory_profiler
Python1 2 3 4 5 6 7 8@profile def przetwarzaj_dane(data): wynik = [] # Linia 1: 50 MiB for item in data: # Linia 2: 50 MiB wynik.append(item * 2) # Linia 3: 100 MiB return wynik # Linia 4: 100 MiB # Uruchom: python -m memory_profiler script.py
Output:
Bash1 2 3 4 5 6 7 8Line #Mem usageIncrement Line Contents ================================================ 1 50.0 MiB0.0 MiB @profile 2 50.0 MiB0.0 MiB def przetwarzaj_dane(data): 3 50.0 MiB0.0 MiB wynik = [] 4 52.0 MiB2.0 MiB for item in data: 5 75.0 MiB 23.0 MiB wynik.append(item * 2) 6 75.0 MiB0.0 MiB return wynik
py-spy – profilowanie działających procesów
py-spy pozwala profilować działający proces Python bez modyfikacji kodu.
Instalacja py-spy
Bash1pip install py-spy
Użycie py-spy
Bash1 2 3 4 5 6 7 8# Profilowanie działającego procesu py-spy top --pid 12345 # Zapisz profil do pliku py-spy record -o profile.svg --pid 12345 # Real-time profilowanie py-spy top --pid 12345 --rate 100
To szczególnie przydatne do profilowania aplikacji produkcyjnych bez restartowania.
Profilowanie asynchronicznego kodu
Profilowanie kodu asynchronicznego wymaga specjalnego podejścia. Więcej o asynchroniczności i jej optymalizacji znajdziesz w artykule Asynchroniczność w Pythonie z asyncio.
cProfile z asyncio
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22import asyncio import cProfile import pstats async def async_task(): await asyncio.sleep(0.1) return sum(range(1000)) async def main(): profiler = cProfile.Profile() profiler.enable() tasks = [async_task() for _ in range(100)] await asyncio.gather(*tasks) profiler.disable() stats = pstats.Stats(profiler) stats.sort_stats('cumulative') stats.print_stats() asyncio.run(main())
Więcej o asynchroniczności w Asynchroniczność w Pythonie z asyncio.
Najlepsze praktyki profilowania
1. Profiluj, nie zgaduj
Zawsze mierz wydajność przed optymalizacją. Intuicja często jest błędna.
2. Profiluj realistyczne dane
Używaj danych podobnych do produkcyjnych. Małe dane testowe mogą nie pokazać problemów.
3. Profiluj w odpowiednim środowisku
- Development – dla szybkiego feedbacku
- Staging – dla bardziej realistycznych wyników
- Production – tylko jeśli bezpieczne (np. py-spy)
4. Szukaj wzorców
Nie tylko pojedyncze wolne funkcje, ale też:
- Funkcje wywoływane bardzo często (nawet jeśli szybkie)
- Duże alokacje pamięci
- N+1 queries (w przypadku baz danych)
5. Mierz przed i po
Zawsze mierz wydajność przed i po optymalizacji, aby potwierdzić poprawę. Profilowanie jest szczególnie przydatne przy optymalizacji wielowątkowych i wieloprocesowych aplikacji — więcej w Wielowątkowość a wieloprocesowość w Pythonie.
6. Uważaj na overhead profilowania
Profilowanie spowalnia kod (zwykle 2-10x). To normalne, ale pamiętaj o tym przy interpretacji wyników.
Przykład praktyczny: optymalizacja funkcji
Załóżmy, że mamy funkcję do przetwarzania danych:
Python1 2 3 4 5 6 7 8def przetwarzaj_dane(data): wynik = [] for item in data: if item > 0: wynik.append(item * 2) else: wynik.append(0) return wynik
Krok 1: Profilowanie
Python1 2 3 4 5 6 7 8 9 10 11from line_profiler import LineProfiler profiler = LineProfiler() profiler.add_function(przetwarzaj_dane) profiler.enable() data = list(range(100000)) wynik = przetwarzaj_dane(data) profiler.disable() profiler.print_stats()
Krok 2: Analiza wyników
Widzimy, że pętla i append są wąskim gardłem.
Krok 3: Optymalizacja
Python1 2def przetwarzaj_dane_opt(data): return [item * 2 if item > 0 else 0 for item in data]
Krok 4: Porównanie
Python1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24import timeit setup = """ data = list(range(100000)) def przetwarzaj_dane(data): wynik = [] for item in data: if item > 0: wynik.append(item * 2) else: wynik.append(0) return wynik def przetwarzaj_dane_opt(data): return [item * 2 if item > 0 else 0 for item in data] """ czas_stary = timeit.timeit('przetwarzaj_dane(data)', setup=setup, number=100) czas_nowy = timeit.timeit('przetwarzaj_dane_opt(data)', setup=setup, number=100) print(f"Stary: {czas_stary:.4f}s") print(f"Nowy: {czas_nowy:.4f}s") print(f"Poprawa: {(1 - czas_nowy/czas_stary)*100:.1f}%")
Integracja z CI/CD
Możesz automatycznie profilować kod w CI/CD:
YAML1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26# .github/workflows/profiling.yml name: Performance Profiling on: [pull_request] jobs: profile: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: '3.11' - name: Install dependencies run: pip install -r requirements.txt pytest cProfile - name: Run profiling run: | python -m cProfile -o profile.prof -m pytest tests/ - name: Analyze profile run: | python -c "import pstats; s = pstats.Stats('profile.prof'); s.sort_stats('cumulative'); s.print_stats(20)"
Podsumowanie
Profilowanie to kluczowa umiejętność dla każdego programisty Pythona. Kluczowe narzędzia:
- ✅ timeit – szybkie pomiary czasu wykonania
- ✅ cProfile – statystyczne profilowanie całych programów
- ✅ line_profiler – profilowanie linia po linii
- ✅ memory_profiler – analiza zużycia pamięci
- ✅ py-spy – profilowanie działających procesów
Pamiętaj: zawsze mierz przed optymalizacją, profiluj na realistycznych danych i mierz postępy po optymalizacji.
Co dalej?
Rozwijaj umiejętności optymalizacji:
- Zaawansowane techniki w Pythonie – ogólne techniki optymalizacji
- Asynchroniczność w Pythonie z asyncio – optymalizacja aplikacji asynchronicznych
- Zaawansowane zarządzanie pamięcią i garbage collector – optymalizacja użycia pamięci
Pamiętaj: profilowanie to proces iteracyjny. Znajdź problem, napraw go, zmierz poprawę i powtórz!



