Бенчмаркинг и профилирование
В этой главе мы узнаем, как тестирование производительности и профилирование помогают в решении проблем с производительностью.
Предположим, мы написали код, и он тоже дает желаемый результат, но что, если мы хотим запустить этот код немного быстрее, потому что потребности изменились. В этом случае нам нужно выяснить, какие части нашего кода замедляют работу всей программы. В этом случае могут быть полезны сравнительный анализ и профилирование.
Что такое сравнительный анализ?
Бенчмаркинг направлен на оценку чего-либо по сравнению со стандартом. Однако здесь возникает вопрос: что это за бенчмаркинг и зачем он нам нужен в случае программирования. Тестирование кода означает, насколько быстро выполняется код и где находится узкое место. Одна из основных причин тестирования производительности заключается в том, что он оптимизирует код.
Как работает бенчмаркинг?
Если мы говорим о работе эталонного тестирования, нам нужно начать с эталонного тестирования всей программы как одного текущего состояния, затем мы можем объединить микро эталонные тесты, а затем разложить программу на более мелкие программы. Чтобы найти узкие места в нашей программе и оптимизировать ее. Другими словами, мы можем понять это как разбиение большой и сложной проблемы на серии более мелких и немного более простых задач для их оптимизации.
Модуль Python для тестирования производительности
В Python у нас есть модуль по умолчанию для тестирования производительности, который называется timeit. С помощьюtimeit модуль, мы можем измерить производительность небольшого фрагмента кода Python в нашей основной программе.
пример
В следующем скрипте Python мы импортируем timeit модуль, который дополнительно измеряет время, необходимое для выполнения двух функций: functionA а также functionB -
import timeit
import time
def functionA():
print("Function A starts the execution:")
print("Function A completes the execution:")
def functionB():
print("Function B starts the execution")
print("Function B completes the execution")
start_time = timeit.default_timer()
functionA()
print(timeit.default_timer() - start_time)
start_time = timeit.default_timer()
functionB()
print(timeit.default_timer() - start_time)
После запуска вышеуказанного скрипта мы получим время выполнения обеих функций, как показано ниже.
Выход
Function A starts the execution:
Function A completes the execution:
0.0014599495514175942
Function B starts the execution
Function B completes the execution
0.0017024724827479076
Написание собственного таймера с помощью функции декоратора
В Python мы можем создать собственный таймер, который будет действовать так же, как и timeitмодуль. Это можно сделать с помощьюdecoratorфункция. Ниже приведен пример настраиваемого таймера -
import random
import time
def timer_func(func):
def function_timer(*args, **kwargs):
start = time.time()
value = func(*args, **kwargs)
end = time.time()
runtime = end - start
msg = "{func} took {time} seconds to complete its execution."
print(msg.format(func = func.__name__,time = runtime))
return value
return function_timer
@timer_func
def Myfunction():
for x in range(5):
sleep_time = random.choice(range(1,3))
time.sleep(sleep_time)
if __name__ == '__main__':
Myfunction()
Вышеупомянутый скрипт python помогает импортировать случайные модули времени. Мы создали функцию-декоратор timer_func (). Внутри него есть функция function_timer (). Теперь вложенная функция захватит время перед вызовом переданной функции. Затем он ожидает возврата функции и захватывает время окончания. Таким образом, мы, наконец, можем заставить скрипт Python печатать время выполнения. Сценарий сгенерирует вывод, как показано ниже.
Выход
Myfunction took 8.000457763671875 seconds to complete its execution.
Что такое профилирование?
Иногда программист хочет измерить некоторые атрибуты, такие как использование памяти, временная сложность или использование определенных инструкций о программах, чтобы измерить реальные возможности этой программы. Такой вид измерения программы называется профилированием. Профилирование использует динамический программный анализ для выполнения таких измерений.
В следующих разделах мы узнаем о различных модулях Python для профилирования.
cProfile - встроенный модуль
cProfile- это встроенный модуль Python для профилирования. Модуль представляет собой C-расширение с разумными накладными расходами, что делает его пригодным для профилирования долго выполняющихся программ. После его запуска он регистрирует все функции и время выполнения. Это очень мощно, но иногда немного сложно интерпретировать и действовать. В следующем примере мы используем cProfile в приведенном ниже коде -
пример
def increment_global():
global x
x += 1
def taskofThread(lock):
for _ in range(50000):
lock.acquire()
increment_global()
lock.release()
def main():
global x
x = 0
lock = threading.Lock()
t1 = threading.Thread(target=taskofThread, args=(lock,))
t2 = threading.Thread(target= taskofThread, args=(lock,))
t1.start()
t2.start()
t1.join()
t2.join()
if __name__ == "__main__":
for i in range(5):
main()
print("x = {1} after Iteration {0}".format(i,x))
Приведенный выше код сохраняется в thread_increment.pyфайл. Теперь выполните код с помощью cProfile в командной строке следующим образом:
(base) D:\ProgramData>python -m cProfile thread_increment.py
x = 100000 after Iteration 0
x = 100000 after Iteration 1
x = 100000 after Iteration 2
x = 100000 after Iteration 3
x = 100000 after Iteration 4
3577 function calls (3522 primitive calls) in 1.688 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:103(release)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:143(__init__)
5 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:147(__enter__)
… … … …
Из приведенного выше вывода ясно, что cProfile распечатывает все 3577 вызванных функций с указанием времени, затраченного на каждую, и количества их вызовов. Ниже приведены столбцы, которые мы получили на выходе -
ncalls - Это количество сделанных звонков.
tottime - Это общее время, потраченное на данную функцию.
percall - Это отношение общего времени к ncalls.
cumtime- Это совокупное время, потраченное на эту и все подфункции. Это верно даже для рекурсивных функций.
percall - Это частное от суммарного времени деления на примитивные звонки.
filename:lineno(function) - Он в основном предоставляет соответствующие данные для каждой функции.