Наглядная демонстрация модуля timeit

Вы когда-нибудь задумывались, медленнее ли вызывать функцию с ключевыми словами, чем без них? Другими словами: что быстрее; позиционные аргументы (myfunc('mike', 33)) или kwargs (myfunc(name='mike', age=33))?

В этой короткой и простой статье мы узнаем, стоит ли жертвовать удобочитаемостью передачи аргументов ключевых слов по сравнению с передачей аргументов позиционно. Настраиваем бенчмарк с модулем timeit и сравниваем результаты. Ничего слишком сложного; давайте кодировать!

Функция

Чтобы оценить производительность вызовов функций, нам сначала нужна функция, которую мы можем вызвать:

def the_func(arg1, arg2):
    pass

Функция не содержит ничего, кроме pass, что означает, что сама по себе функция ничего не делает. Это гарантирует, что мы можем выделить и сравнить способ вызова функции (то есть позиционно или с помощью kwargs).



Сценарий бенчмаркинга

Далее нам понадобится код, который вызывает функцию несколько раз и записывает время, необходимое для ее выполнения:

import timeit

number = 25_000_000
repeat = 10

times_pos: [float] = timeit.repeat(stmt="func('hello', 'world')", globals={'func': the_func}, number=number, repeat=repeat)
times_kwarg: [float] = timeit.repeat(stmt="func(arg1='hello', arg2='world')", globals={'func': the_func}, number=number, repeat=repeat)

Здесь на помощь приходит модуль timeit. Мы используем timeit.repeat для определения времени запуска функции number раз. Мы определяем вызов функции в stmt и сопоставляем func в stmt с функцией, которую мы определили ранее, используя globals dict. Затем мы повторяем эксперимент несколько раз, используя аргумент repeat. В итоге мы получаем массив из 10 чисел с плавающей запятой, каждое из которых представляет выполнение вызова функции 25 миллионов раз.



Проверка результатов

В приведенном ниже коде мы берем список чисел с плавающей запятой, которые нам предоставляет timeit, и отображаем минимальное, максимальное и среднее время выполнения.

print("\t\t\t min (s) \t max (s) \t avg (s)")
print(f"pos: \t\t {min(times_pos):.5f} \t {max(times_pos):.5f} \t {sum(times_pos) / len(times_pos):.5f}")
print(f"arg only: \t {min(times_arg_only):.5f} \t {max(times_arg_only):.5f} \t {sum(times_arg_only) / len(times_arg_only):.5f}")

Сделанный! Давайте проведем сравнительный анализ!



Результаты

Результаты в:

            min (s)   max (s)   avg (s)
pos:        1.38941   1.72278   1.58808
kwarg:      1.72834   1.76344   1.75132

          min (s)   max (s)   avg (s)
pos:      2.07883   2.77485   2.35694
kwarg:    2.05186   3.05402   2.74669

Кажется, что в среднем позиционные аргументы быстрее на 0,39 секунды или чуть более 16,5%. Однако помните, что позиционные аргументы на 0,39 секунды быстрее вызывают функцию 25 миллионов раз.

Это означает, что выбор позиционных аргументов вместо аргументов ключевого слова сэкономит вам около 16 наносекунд; время, за которое свет проходит расстояние около 4,8 метра (16 футов).



Заключение

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

Я надеюсь, что эта статья была настолько ясной, насколько я надеюсь, но если это не так, пожалуйста, дайте мне знать, что я могу сделать, чтобы прояснить ситуацию. А пока ознакомьтесь с моими другими статьями на всевозможные темы, связанные с программированием, например:

Удачного кодирования!

— Майк

P.S. Нравится, что я делаю? Следуй за мной!