При написании программы, разработчик примерно представляет себе, как должна работать его программа. Но не всегда его ожидания соответствуют действительности — приложения тормозят, потребляют много ресурсов и вообще ведут себя не так, как задумывалось, особенно под большой нагрузкой. В своём докладе я покажу, как заглянуть "под капот" ваших приложений на Python (и Django в частности): какие способы профилирования бывают и когда их можно использовать, расскажу об отладке приложений и различных инструментах, которые помогают разработчику при разработке.
2. Moscow Django MeetUp №13
Обо мне
• технический руководитель
Календаря Mail.Ru
• pythonista
• бывший перловик
• ленивый программист
3. Moscow Django MeetUp №13
Cбор характеристик работы программы
с целью их дальнейшей оптимизации.
Профилирование
4. Moscow Django MeetUp №13
Что собираем?
• время выполнения строк кода
• количество вызовов функций
• время выполнения функций
• дерево вызовов функций
• “hot spots”
• загрузку CPU, использование памяти
• и т.д.
6. Moscow Django MeetUp №13
Простые делители числа 13195 — это 5, 7, 13 и 29.
Какой самый большой делитель числа 600851475143,
являющийся простым числом?
Project Euler: задача 3
7. Moscow Django MeetUp №13
1 """Project Euler problem 3 solve"""
2
3
4 def is_prime(num):
5 """Checks if num is prime number"""
6 for i in range(2, num):
7 if not num % i:
8 return False
9 return True
10
11
12 def prime_factors(num):
13 """Find prime factors of num"""
14 result = []
15 for i in range(2, num):
16 if is_prime(i) and not num % i:
17 result.append(i)
18 return result
19
20
21 if __name__ == '__main__':
22 print "Answer: %d" % prime_factors(600851475143)[-1]
Project Euler: задача 3
10. Moscow Django MeetUp №13
Профилирование начинается в голове.
Инструменты — всего лишь инструменты.
Искусство профилирования
11. Moscow Django MeetUp №13
1 """Project Euler problem 3 solve"""
2 from math import sqrt
3
4
5 def is_prime(num):
6 """Checks if num is prime number"""
7 for i in range(2, int(sqrt(num)) + 1):
8 if not num % i:
9 return False
10 return True
11
12
13 def prime_factors(num):
14 """Find prime factors of num"""
15 result = []
16 for i in range(2, int(sqrt(num)) + 1):
17 if is_prime(i) and not num % i:
18 result.append(i)
19 return result
20
21
22 if __name__ == '__main__':
23 print "Answer: %d" % prime_factors(600851475143)[-1]
Project Euler: задача 3
12. Moscow Django MeetUp №13
Подходы к профилированию
• ручное профилирование
• с помощью инструментов
14. Moscow Django MeetUp №13
rudnyh@work:~/work/python-profiling (venv: python-profiling) (git: master|✔)
➜ time python max_prime_factor.py
Answer: 6857
python max_prime_factor.py 20,00s user 0,19s system 82% cpu 24,445 total
Ручное профилирование
15. Moscow Django MeetUp №13
1 """Project Euler problem 3 solve"""
2 import time
3 from math import sqrt
4
5
6 def is_prime(num):
7 """Checks if num is prime number"""
8 for i in range(2, int(sqrt(num)) + 1):
9 if not num % i:
10 return False
11 return True
12
13
14 def prime_factors(num):
15 """Find prime factors of num"""
16 result = []
17 for i in range(2, int(sqrt(num)) + 1):
18 if is_prime(i) and not num % i:
19 result.append(i)
20 return result
21
22
23 if __name__ == '__main__':
24 start = time.time()
25 print "Answer: %d" % prime_factors(600851475143)[-1]
26 print "Time: %f s" % (time.time() - start)
Ручное профилирование
20. Moscow Django MeetUp №13
Ручное профилирование
очень простое применение
ограниченно подходит
для продакшена
вставка чужеродного кода в проект
никакой информации о коде
(кроме времени выполнения)
анализ результатов может быть
затруднительным
25. Moscow Django MeetUp №13
from django.core.mail import send_mail
from profiling.context_managers import Stats
...
with Stats('django_project.profiling.send_email'):
send_mail(
'Subject here',
'Here is the message.',
'from@example.com',
['to@example.com'],
fail_silently=False
)
Пример использования
27. Moscow Django MeetUp №13
from django.db import models
from profiling.decorator import stats
...
class Model(model.Model):
...
@stats('django_project.profiling.application.save')
def save(self, *args, **kwargs):
# do some hard work like thumbnail generation
super(Model, self).save(*args, **kwargs)
Пример использования
31. Moscow Django MeetUp №13
def is_prime(num):
"""Checks if num is prime number"""
for i in range(2, num):
if not num % i:
return False
return True
Deterministic profilers
@profile
def is_prime(num):
"""Checks if num is prime number"""
for i in range(2, num):
if not num % i:
return False
return True
32. Moscow Django MeetUp №13
def is_prime(num):
"""Checks if num is prime number"""
with profile():
for i in range(2, num):
if not num % i:
return False
return True
Deterministic profilers
def is_prime(num):
"""Checks if num is prime number"""
profile.start()
for i in range(2, num):
if not num % i:
return False
profile.stop()
return True
34. Moscow Django MeetUp №13
Deterministic profilers
вся информация о коде
множество инструментов для анализа
очень медленно
непригодно для продакшена
(или крайне ограниченно)
37. Moscow Django MeetUp №13
Statistical profilers
можно пускать в продакшн
(практически не влияет на быстродействие)
не вся информация о коде
мало инструментов для анализа
41. Moscow Django MeetUp №13
1 """Project Euler problem 3 solve"""
2 from math import sqrt
3
4
5 def is_prime(num):
6 """Checks if num is prime number"""
7 for i in range(2, int(sqrt(num)) + 1):
8 if not num % i:
9 return False
10 return True
11
12
13 def prime_factors(num):
14 """Find prime factors of num"""
15 result = []
16 for i in range(2, int(sqrt(num)) + 1):
17 if is_prime(i) and not num % i:
18 result.append(i)
19 return result
20
21
22 if __name__ == '__main__':
23 print "Answer: %d" % prime_factors(600851475143)[-1]
Project Euler: задача 3
42. Moscow Django MeetUp №13
1 """Project Euler problem 3 solve"""
2 from math import sqrt
3
4
5 def is_prime(num):
6 """Checks if num is prime number"""
7 for i in range(2, int(sqrt(num)) + 1):
8 if not num % i:
9 return False
10 return True
11
12
13 def prime_factors(num):
14 """Find prime factors of num"""
15 result = []
16 for i in range(2, int(sqrt(num)) + 1):
17 if not num % i and is_prime(i):
18 result.append(i)
19 return result
20
21
22 if __name__ == '__main__':
23 print "Answer: %d" % prime_factors(600851475143)[-1]
Project Euler: задача 3
45. Moscow Django MeetUp №13
import cProfile
def profile(func):
"""Decorator for run function profile"""
def wrapper(*args, **kwargs):
profile_filename = func.__name__ + '.profile'
profiler = cProfile.Profile()
result = profiler.runcall(func, *args, **kwargs)
profiler.dump_stats(profile_filename)
return result
return wrapper
@profile
def foo():
...
cProfile: декоратор
46. Moscow Django MeetUp №13
hotshot
http://docs.python.org/2/library/hotshot.html
Инструменты для профилирования
47. Moscow Django MeetUp №13
import hotshot
prof = hotshot.Profile("profile_name.prof")
prof.start()
# your code goes here
prof.stop()
prof.close()
hotshot: использование
48. Moscow Django MeetUp №13
import hotshot
def profile(func):
"""Decorator for run function profile"""
def wrapper(*args, **kwargs):
profile_filename = func.__name__ + '.profile'
profiler = hotshot.Profile(profile_filename)
profiler.start()
result = func(*args, **kwargs)
profiler.stop()
profiler.close()
return result
return wrapper
@profile
def foo():
...
hotshot: декоратор
49. Moscow Django MeetUp №13
cProfile, hotshot
мощные инструменты
достаточно простые
дерево вызовов функций
сильно влияют на производительность
анализ результатов может быть
затруднительным
50. Moscow Django MeetUp №13
rudnyh@work:~/work/python-profiling (venv: python-profiling) (git: master|✔)
➜ time python max_prime_factor.py
Answer: 6857
python max_prime_factor.py 0,18s user 0,01s system 95% cpu 0,199 total
rudnyh@work:~/work/python-profiling (venv: python-profiling) (git: master|✔)
➜ time python -m cProfile max_prime_factor.py
Answer: 6857
22 function calls in 0.252 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.005 0.005 0.252 0.252 max_prime_factor.py:1(<module>)
1 0.247 0.247 0.247 0.247 max_prime_factor.py:13(prime_factors)
7 0.000 0.000 0.000 0.000 max_prime_factor.py:5(is_prime)
8 0.000 0.000 0.000 0.000 {math.sqrt}
4 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
python -m cProfile max_prime_factor.py 0,23s user 0,09s system 58% cpu 0,541 total
cProfile: производительность
51. Moscow Django MeetUp №13
Сохраняем результаты профилирования
в файл для дальнейшего анализа:
rudnyh@work:~/work/python-profiling (venv: python-profiling) (git: master|✔)
➜ python -m cProfile -o max_prime_factor.prof max_prime_factor.py
Answer: 6857
rudnyh@work:~/work/python-profiling (venv: python-profiling) (git: master|✔)
➜ ls
max_prime_factor.prof max_prime_factor.py
cProfile: анализ результатов
52. Moscow Django MeetUp №13
rudnyh@work:~/work/python-profiling (venv: python-profiling) (git: master|✔)
➜ ipython
In [1]: import pstats
In [2]: p = pstats.Stats('max_prime_factor.prof')
In [3]: p.sort_stats('calls')
Out[3]: <pstats.Stats instance at 0x10689bf80>
In [4]: p.print_stats(5)
22 function calls in 0.166 seconds
Ordered by: call count
List reduced from 6 to 5 due to restriction <5>
ncalls tottime percall cumtime percall filename:lineno(function)
8 0.000 0.000 0.000 0.000 {math.sqrt}
7 0.000 0.000 0.000 0.000 max_prime_factor.py:5(is_prime)
4 0.000 0.000 0.000 0.000 {method 'append' of 'list' objects}
1 0.003 0.003 0.166 0.166 max_prime_factor.py:1(<module>)
1 0.163 0.163 0.163 0.163 max_prime_factor.py:13(prime_factors)
Out[4]: <pstats.Stats instance at 0x10689bf80>
Анализ: pstats
69. Moscow Django MeetUp №13
import pycallgraph
pycallgraph.start_trace()
# your code goes here
pycallgraph.make_dot_graph('call-graph.png')
pycallgraph
70. Moscow Django MeetUp №13
line_profiler
https://bitbucket.org/robertkern/line_profiler
Инструменты для профилирования
71. Moscow Django MeetUp №13
rudnyh@work:~/work/python-profiling (venv: python-profiling) (git: master|✔)
➜ kernprof.py -v -l max_prime_factor.py
Function: is_prime at line 5
Total time: 0.001412 s
Line # Hits Time Per Hit % Time Line Contents
==============================================================
6 def is_prime(num):
7 """Checks if num is prime number"""
8 366 767 2.1 54.3 for i in range(2, int(sqrt(num)) + 1):
9 362 614 1.7 43.5 if not num % i:
10 3 23 7.7 1.6 return False
11 4 8 2.0 0.6 return True
Function: prime_factors at line 14
Total time: 2.67703 s
Line # Hits Time Per Hit % Time Line Contents
==============================================================
15 def prime_factors(num):
16 """Find prime factors of num"""
17 1 4 4.0 0.0 result = []
18 775146 1320370 1.7 49.3 for i in range(2, int(sqrt(num)) + 1):
19 775145 1356632 1.8 50.7 if not num % i and is_prime(i):
20 4 16 4.0 0.0 result.append(i)
21 1 10 10.0 0.0 return result
line_profiler
72. Moscow Django MeetUp №13
memory_profiler
https://github.com/fabianp/memory_profiler
Инструменты для профилирования
73. Moscow Django MeetUp №13
rudnyh@work:~/work/python-profiling (venv: python-profiling) (git: master|✔)
➜ python -m memory_profiler max_prime_factor.py
Line # Mem usage Increment Line Contents
================================================
6 def is_prime(num):
7 32.469 MB 0.000 MB """Checks if num is prime number"""
8 57.414 MB 24.945 MB for i in range(2, int(sqrt(num)) + 1):
9 57.414 MB 0.000 MB if not num % i:
10 32.566 MB -24.848 MB return False
11 33.047 MB 0.480 MB return True
Line # Mem usage Increment Line Contents
================================================
15 def prime_factors(num):
16 8.379 MB 0.000 MB """Find prime factors of num"""
17 8.379 MB 0.000 MB result = []
18 75.332 MB 66.953 MB for i in range(2, int(sqrt(num)) + 1):
19 33.047 MB -42.285 MB if not num % i and is_prime(i):
20 75.332 MB 42.285 MB result.append(i)
21 75.332 MB 0.000 MB return result
memory_profiler
74. Moscow Django MeetUp №13
1 """Project Euler problem 3 solve"""
2 from math import sqrt
3
4
5 def is_prime(num):
6 """Checks if num is prime number"""
7 for i in xrange(2, int(sqrt(num)) + 1):
8 if not num % i:
9 return False
10 return True
11
12
13 def prime_factors(num):
14 """Find prime factors of num"""
15 result = []
16 for i in xrange(2, int(sqrt(num)) + 1):
17 if not num % i and is_prime(i):
18 result.append(i)
19 return result
20
21
22 if __name__ == '__main__':
23 print "Answer: %d" % prime_factors(600851475143)[-1]
Project Euler: задача 3
75. Moscow Django MeetUp №13
rudnyh@work:~/work/python-profiling (venv: python-profiling) (git: master|✔)
➜ python -m memory_profiler max_prime_factor.py
Line # Mem usage Increment Line Contents
================================================
6 def is_prime(num):
7 8.391 MB 0.000 MB """Checks if num is prime number"""
8 22.605 MB 14.215 MB for i in xrange(2, int(sqrt(num)) + 1):
9 22.605 MB 0.000 MB if not num % i:
10 8.484 MB -14.121 MB return False
11 8.965 MB 0.480 MB return True
Line # Mem usage Increment Line Contents
================================================
15 def prime_factors(num):
16 8.379 MB 0.000 MB """Find prime factors of num"""
17 8.379 MB 0.000 MB result = []
18 34.141 MB 25.762 MB for i in xrange(2, int(sqrt(num)) + 1):
19 8.965 MB -25.176 MB if not num % i and is_prime(i):
20 34.141 MB 25.176 MB result.append(i)
21 34.141 MB 0.000 MB return result
memory_profiler
90. Moscow Django MeetUp №13
statprof
небольшой оверхед
можно пускать в продакшн
(если осторожно)
профилирование SQL-запросов
сложная установка, зависимости
мало данных на выходе
91. Moscow Django MeetUp №13
plop
https://github.com/bdarnell/plop
Инструменты для профилирования
102. Moscow Django MeetUp №13
New Relic
предназначен для продакшена
огромный функционал
платный (есть бесплатная версия)
данные отправляются на чужие
серверы
108. Moscow Django MeetUp №13
Django Debug Toolbar
прост в установке и использовании
огромный функционал
расширяемый (плагины)
не для продакшена (?)
данные никуда не сохраняются
(только просмотр в реальном времени)
112. Moscow Django MeetUp №13
Django StatsD
прост в установке и использовании
используется в продакшене
мало информации (количество/время)
Нужен graphite и statsd (must-have)
114. Moscow Django MeetUp №13
The Python Debugger
http://docs.python.org/2/library/pdb.html
Инструменты для отладки
115. Moscow Django MeetUp №13
rudnyh@work:~/work/python-profiling (venv: python-profiling) (git: master|✔)
➜ python -m pdb max_prime_factor.py
> /home/rudnyh/work/python-profiling/max_prime_factor.py(1)<module>()
-> """Project Euler problem 3 solve"""
(Pdb) next
> /home/rudnyh/work/python-profiling/max_prime_factor.py(2)<module>()
-> from math import sqrt
(Pdb) next
> /home/rudnyh/work/python-profiling/max_prime_factor.py(5)<module>()
-> def is_prime(num):
(Pdb) next
> /home/rudnyh/work/python-profiling/max_prime_factor.py(13)<module>()
-> def prime_factors(num):
(Pdb) next
> /home/rudnyh/work/python-profiling/max_prime_factor.py(22)<module>()
-> if __name__ == '__main__':
(Pdb) next
> /home/rudnyh/work/python-profiling/max_prime_factor.py(23)<module>()
-> print "Answer: %d" % prime_factors(600851475143)[-1]
(Pdb) next
Answer: 6857
--Return--
> /home/rudnyh/work/python-profiling/max_prime_factor.py(23)<module>()->None
-> print "Answer: %d" % prime_factors(600851475143)[-1]
(Pdb) quit
pdb
116. Moscow Django MeetUp №13
1 """Project Euler problem 3 solve"""
2 from math import sqrt
3
4
5 def is_prime(num):
6 """Checks if num is prime number"""
7 import pdb; pdb.set_trace()
8 for i in xrange(2, int(sqrt(num)) + 1):
9 if not num % i:
10 return False
11 return True
12
13
14 def prime_factors(num):
15 """Find prime factors of num"""
16 result = []
17 for i in xrange(2, int(sqrt(num)) + 1):
18 if not num % i and is_prime(i):
19 result.append(i)
20 return result
21
22
23 if __name__ == '__main__':
24 print "Answer: %d" % prime_factors(600851475143)[-1]
pdb
117. Moscow Django MeetUp №13
rudnyh@work:~/work/python-profiling (venv: python-profiling) (git: master|✔)
➜ python max_prime_factor.py
> /home/rudnyh/work/python-profiling/max_prime_factor.py(8)is_prime()
-> for i in xrange(2, int(sqrt(num)) + 1):
(Pdb) list
3
4
5 def is_prime(num):
6 """Checks if num is prime number"""
7 import pdb; pdb.set_trace()
8 -> for i in xrange(2, int(sqrt(num)) + 1):
9 if not num % i:
10 return False
11 return True
12
13
(Pdb) next
> /home/rudnyh/work/python-profiling/max_prime_factor.py(9)is_prime()
-> if not num % i:
(Pdb) continue
> /home/rudnyh/work/python-profiling/max_prime_factor.py(8)is_prime()
-> for i in xrange(2, int(sqrt(num)) + 1):
(Pdb)
pdb
118. Moscow Django MeetUp №13
pdb
практически неограниченный функционал
не очень удобно
119. Moscow Django MeetUp №13
django-pdb
https://github.com/tomchristie/django-pdb
Инструменты для отладки