Недавно было обнаружено, что запрос API проекта был относительно медленным, путем захвата пакетов было обнаружено, что время ответа слишком велико, поэтому начались работы по оптимизации. Ключевым этапом работы по оптимизации являетсяНайдите узкое место проблемы, для скорости оптимизации рекомендуется сократить время выполнения функции оптимизации.
Эта статья посвящена анализу, а не тому, как оптимизировать
острое оружие
Если вы хотите сделать работу хорошо, вы должны сначала наточить свои инструменты.Нужен набор удобных и эффективных инструментов для учета времени работы функций. Говорят, что это набор инструментов, но для простого проекта или ежедневной разработки достаточно реализовать класс инструментов.Поскольку реализация относительно проста, кодируйте напрямую:
from functools import wraps
import cProfile
from line_profiler import LineProfiler
import time
def func_time(f):
"""
简单记录执行时间
:param f:
:return:
"""
@wraps(f)
def wrapper(*args, **kwargs):
start = time.time()
result = f(*args, **kwargs)
end = time.time()
print f.__name__, 'took', end - start, 'seconds'
return result
return wrapper
def func_cprofile(f):
"""
内建分析器
"""
@wraps(f)
def wrapper(*args, **kwargs):
profile = cProfile.Profile()
try:
profile.enable()
result = f(*args, **kwargs)
profile.disable()
return result
finally:
profile.print_stats(sort='time')
return wrapper
try:
from line_profiler import LineProfiler
def func_line_time(follow=[]):
"""
每行代码执行时间详细报告
:param follow: 内部调用方法
:return:
"""
def decorate(func):
@wraps(func)
def profiled_func(*args, **kwargs):
try:
profiler = LineProfiler()
profiler.add_function(func)
for f in follow:
profiler.add_function(f)
profiler.enable_by_count()
return func(*args, **kwargs)
finally:
profiler.print_stats()
return profiled_func
return decorate
except ImportError:
def func_line_time(follow=[]):
"Helpful if you accidentally leave in production!"
def decorate(func):
@wraps(func)
def nothing(*args, **kwargs):
return func(*args, **kwargs)
return nothing
return decorate
Исходный код может относиться кgist
Таким образом, реализованы три функции декоратора.func_time,func_cprofile,func_line_time, соответствующий
- Время выполнения простой функции вывода
- Используйте встроенный пакет анализа, который поставляется с Python.
cProfileАнализ, который в основном подсчитывает вызовы функций и процессорное время, занимаемое каждой функцией. - использовать
line_profilerПроект с открытым исходным кодом, он может подсчитывать время выполнения и время выполнения каждой строки кода.
Инструкции по применению
Давайте проиллюстрируем это на примере простого цикла,
def test():
for x in range(10000000):
print x
func_time
оfunc_timeДумаю тут нечего сказать, достаточно просто вывести время вызова функции, которое можно использовать, когда мы грубо посчитаем время выполнения функции
следующее:
@func_time
def test():
for x in range(10000000):
print x
# 输出
test took 6.10190296173 seconds
func_cprofile
cProfileЭто встроенный пакет Python, расширенное приложение, реализованное на языке C на основе lsprof, эксплуатационные расходы относительно разумны, нет внешней зависимости, и он подходит для быстрых итоговых тестов.
@func_cprofile
def test():
for x in range(10000000):
print x
# 输出
3 function calls in 6.249 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
1 6.022 6.022 6.249 6.249 test.py:41(test)
1 0.227 0.227 0.227 0.227 {range}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Описание выхода:
в секундах
-
Первая строка сообщает нам, что всего было вызвано 3 функции.
Обычный процесс разработки, первая строка больше похожа на вывод
194 function calls (189 primiive calls) in 0.249 seconds, (189 примитивных вызовов) указывает, что 189 являются примитивными вызовами, указывая на то, что эти вызовы не включают рекурсию -
ncalls представляет собой количество вызовов функции.Если в этом столбце два значения, это означает, что есть рекурсивные вызовы.Первое — общее количество вызовов, а второе — количество нативных вызовов.
-
tottime — это общее время, проведенное внутри функции (исключая время, затраченное на вызов других функций).
-
percall — это tottime, деленное на ncall, которое представляет собой среднее время, прошедшее за один вызов.
-
cumtime — это кумулятивная сумма прошедшего времени всех предыдущих подфункций.
-
percall — это cumtime, деленное на количество собственных вызовов, что означает среднее время использования каждого собственного вызова при вызове функции.
-
filename:lineno(function) — это имя файла, номер строки и имя анализируемой функции.
func_line_time
line_profilerМожет генерировать очень прямые и подробные отчеты, но это очень дорого и будет на порядок медленнее, чем реальное время выполнения
@func_line_time()
def test():
for x in range(10000000):
print x
# 输出
Timer unit: 1e-06 s
Total time: 14.4183 s
File: /xx/test.py
Function: test at line 41
Line # Hits Time Per Hit % Time Line Contents
==============================================================
41 @func_line_time()
42 def test():
43 10000001 4031936.0 0.4 28.0 for x in range(10000000):
44 10000000 10386347.0 1.0 72.0 print x
Описание выхода:
Единица измерения — микросекунды.
-
Total Time: общее время работы тестового кода.
-
Строка: номер строки кода
-
Хиты: указывает, сколько раз запускалась каждая строка кода.
-
Время: общее время выполнения каждой строки кода.
-
Per Hits: время выполнения каждой строки кода один раз.
-
% Time: процент времени, в течение которого выполнялась каждая строка кода.
Суммировать
В ежедневной разработке вы можете использоватьfunc_time,func_cprofileВыполняйте базовые проверки, определяйте общие проблемы, используйтеfunc_line_timeПроведите более тщательный осмотр.
Примечание:
func_line_timeВы также можете проверить время выполнения функции, вызываемой внутри функции, с помощьюfollowПараметр указывает объявление функции, соответствующее внутреннему вызову.Параметр представляет собой массив, что означает, что вы можете проверить несколько внутренних функций.