Учебники

Бенчмаркинг и профилирование

В этой главе мы узнаем, как бенчмаркинг и профилирование помогают решать проблемы производительности.

Предположим, что мы написали код, и он также дает желаемый результат, но что, если мы хотим запустить этот код немного быстрее, потому что потребности изменились. В этом случае нам необходимо выяснить, какие части нашего кода замедляют работу всей программы. В этом случае могут быть полезны бенчмаркинг и профилирование.

Что такое Бенчмаркинг?

Бенчмаркинг нацелен на оценку чего-либо по сравнению со стандартом. Однако здесь возникает вопрос: что такое тестирование и зачем оно нам нужно в случае программирования? Сравнительный анализ кода означает, насколько быстро выполняется код и где узкое место. Одной из основных причин сравнительного анализа является то, что он оптимизирует код.

Как работает бенчмаркинг?

Если мы говорим о работе бенчмаркинга, нам нужно начать с бенчмаркинга всей программы как единого текущего состояния, тогда мы можем объединить микро бенчмарки, а затем разложить программу на более мелкие программы. Чтобы найти узкие места в нашей программе и оптимизировать ее. Другими словами, мы можем понять это как разбивку большой и сложной проблемы на ряд более мелких и более простых задач для их оптимизации.

Модуль 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 . Это можно сделать с помощью функции декоратора . Ниже приведен пример пользовательского таймера —

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 — количество звонков.

общее время — это общее время, проведенное в данной функции.

percall — относится к коэффициенту totaltime, деленному на ncalls.

cumtime — совокупное время, проведенное в этой и всех подфункциях. Это даже точно для рекурсивных функций.

percall — это частное cumtime, разделенное на примитивные вызовы.

filename: lineno (функция) — в основном предоставляет соответствующие данные каждой функции.