Використання Python Timeit для визначення часу коду

У цій інструкції ви ознайомитеся з використанням функції timeit з модуля timeit в Python. Ви навчитеся вимірювати час виконання окремих виразів і функцій у вашому коді Python.

Аналіз часу виконання коду може допомогти вам оцінити продуктивність різних частин програми та виявити області, що потребують оптимізації.

Спочатку ми розглянемо структуру функції timeit. Потім застосуємо її на практиці, щоб зрозуміти, як вимірювати час виконання блоків коду та функцій у ваших Python-проєктах. Почнемо!

Використання функції timeit в Python

Модуль timeit є частиною стандартної бібліотеки Python. Щоб його використовувати, потрібно імпортувати:

import timeit

Синтаксис функції timeit з модуля timeit виглядає наступним чином:

timeit.timeit(stmt, setup, number)

Де:

  • stmt – це фрагмент коду, час виконання якого потрібно виміряти. Це може бути як простий рядок Python, так і багаторядковий, або ж ім’я функції.
  • setup – це код, який виконується один раз перед вимірюванням часу виконання stmt. Часто використовується для підготовки даних або імпорту необхідних модулів. Наприклад, при вимірюванні часу створення масиву NumPy, імпорт numpy буде кодом setup.
  • number – це кількість разів, яку потрібно виконати stmt. Значення за замовчуванням – 1 мільйон (1 000 000), але ви можете встановити будь-яке інше значення.

Тепер, коли ми розібрали синтаксис timeit(), перейдемо до прикладів коду.

Вимірювання часу простих виразів Python

У цьому розділі ми застосуємо timeit для вимірювання часу виконання простих виразів Python.

Запустіть інтерактивний інтерпретатор Python (REPL) і випробуйте наведені нижче приклади. Ми виміряємо час операцій піднесення до степеня та цілочисельного ділення для 10 000 і 100 000 ітерацій.

Зверніть увагу, що вираз, час виконання якого ми вимірюємо, передається як рядок Python, а різні вирази в рядку розділені крапкою з комою.

>>> import timeit
>>> timeit.timeit('3**4;3//4',number=10000)
0.0004020999999738706

>>> timeit.timeit('3**4;3//4',number=100000)
0.0013780000000451764

Запуск timeit з командного рядка

Функцію timeit можна використовувати і з командного рядка. Ось еквівалентний виклик timeit з командного рядка:

$ python -m timeit -n [number] -s [setup] [stmt]
  • python -m timeit означає, що ми запускаємо timeit як головний модуль.
  • -n – це параметр командного рядка, який визначає кількість разів виконання коду. Відповідає аргументу number у функції timeit().
  • Параметр -s використовується для визначення коду setup.

Перепишемо попередній приклад, використовуючи командний рядок:

$ python -m timeit -n 100000 '3**4;3//4'
100000 loops, best of 5: 35.8 nsec per loop

У цьому прикладі ми вимірюємо час виконання вбудованої функції len(). Ініціалізація рядка – це код setup, який передається через параметр -s.

$ python -m timeit -n 100000 -s "string_1 = 'coding'" 'len(string_1)'
100000 loops, best of 5: 239 nsec per loop

Зверніть увагу, що вихідні дані показують час виконання найкращого з 5 запусків. Коли ви запускаєте timeit з командного рядка, параметр повторення (-r) за замовчуванням дорівнює 5. Це означає, що виконання stmt зазначену кількість разів повторюється 5 разів, і виводиться найкращий час виконання.

Аналіз методів розвороту рядків за допомогою timeit

При роботі з рядками Python іноді потрібно їх розвернути. Існує два основних підходи для розвороту рядка:

  • Використання зрізів рядків
  • Використання функції reversed() та методу join().

Розворот рядків Python за допомогою зрізів

Поглянемо, як працюють зрізи рядків і як їх можна використати для розвороту рядка Python. Синтаксис some_string[start:stop] повертає частину рядка від індексу start до stop-1. Розглянемо приклад.

Нехай є рядок “Python”. Він має довжину 6, а індекси символів – від 0 до 5.

>>> string_1 = 'Python'

Вказуючи початкове та кінцеве значення, ми отримуємо частину рядка. Наприклад, string_1[1:4] поверне 'yth'.

>>> string_1 = 'Python'
>>> string_1[1:4]
'yth'

Якщо початкове значення не вказано, то за замовчуванням використовується 0, і зріз починається з нульового індексу.

Тут кінцеве значення – 3, тому зріз починається з індексу 0 і закінчується на індексі 2.

>>> string_1[:3]
'Pyt'

Якщо ви не вкажете індекс зупинки, зріз почнеться зі вказаного початкового індексу (1) і продовжиться до кінця рядка.

>>> string_1[1:]
'ython'

Ігноруючи як початкове, так і кінцеве значення, ми отримаємо копію всього рядка.

>>> string_1[::]
'Python'

Спробуємо створити зріз із кроком. Встановимо початкове, кінцеве та крокові значення рівними 1, 5 і 2 відповідно. Ми отримаємо частину рядка, починаючи з індексу 1 до індексу 4 (за винятком кінцевої точки 5), що містить кожен другий символ.

>>> string_1[1:5:2]
'yh'

Використовуючи від’ємний крок, можна отримати зріз, починаючи з кінця рядка. З кроком -2, string_1[5:2:-2] дасть наступний зріз:

>>> string_1[5:2:-2]
'nh'

Отже, щоб отримати розгорнуту копію рядка, ми пропускаємо початкове та кінцеве значення і встановлюємо крок у -1, як показано нижче:

>>> string_1[::-1]
'nohtyP'

Коротко: рядок[::-1] повертає перевернуту копію рядка.

Розворот рядків за допомогою вбудованих функцій та методів рядків

Вбудована функція reversed() у Python повертає зворотний ітератор над елементами рядка.

>>> string_1 = 'Python'
>>> reversed(string_1)
<reversed object at 0x00BEAF70>

Ви можете перебрати зворотний ітератор за допомогою циклу for:

for char in reversed(string_1):
    print(char)

І отримати доступ до елементів рядка в зворотному порядку.

# Вивід
n
o
h
t
y
P

Потім можна викликати метод join() для зворотного ітератора: <sep>.join(reversed(some-string)).

Наведений нижче фрагмент коду показує кілька прикладів, де роздільником є ​​дефіс і пробіл відповідно.

>>> '-'.join(reversed(string1))
'n-o-h-t-y-P'
>>> ' '.join(reversed(string1))
'n o h t y P'

Тут нам не потрібен роздільник; тому встановимо роздільник у порожній рядок, щоб отримати перевернуту копію рядка:

>>> ''.join(reversed(string1))
'nohtyP'

Використання .join(reversed(some-string)) повертає перевернуту копію рядка.

Порівняння часу виконання за допомогою timeit

Отже, ми розглянули два способи розвороту рядків Python. Який з них швидший? Давайте з’ясуємо.

У попередньому прикладі з вимірюванням часу простих виразів Python ми не використовували код setup. Зараз ми розгортаємо рядок Python. Операція розвороту виконується певну кількість разів (number), а код setup (ініціалізація рядка) виконується лише один раз.

>>> import timeit
>>> timeit.timeit(stmt="string_1[::-1]", setup = "string_1 = 'Python'", number = 100000)
0.04951830000001678
>>> timeit.timeit(stmt = "''.join(reversed(string_1))", setup = "string_1 = 'Python'", number = 100000)
0.12858760000000302

Для однакової кількості ітерацій (number), підхід із зрізом рядка виявився швидшим, ніж використання методу join() та функції reversed().

Вимірювання часу виконання функцій Python за допомогою timeit

У цьому розділі ми вивчимо, як використовувати функцію timeit для вимірювання часу виконання функцій Python. Маючи список рядків, наступна функція hasDigit повертає список рядків, що містять хоча б одну цифру.

def hasDigit(somelist):
     str_with_digit = []
     for string in somelist:
         check_char = [char.isdigit() for char in string]
         if any(check_char):
            str_with_digit.append(string)
     return str_with_digit

Зараз ми хочемо виміряти час виконання функції hasDigit() за допомогою timeit.

Спочатку визначимо вираз (stmt), час виконання якого ми вимірюватимемо. Це буде виклик функції hasDigit() зі списком рядків як аргумент. Далі визначимо код setup. Здогадуєтесь, що має містити код setup?

Для успішного виклику функції код setup має містити:

  • Визначення функції hasDigit()
  • Ініціалізацію списку рядків (аргументу).

Визначимо код setup у рядку налаштувань, як показано нижче:

setup = """
def hasDigit(somelist):
    str_with_digit = []
    for string in somelist:
      check_char = [char.isdigit() for char in string]
      if any(check_char):
        str_with_digit.append(string)
    return str_with_digit
thislist=['puffin3','7frost','blue']
     """

Тепер ми можемо використовувати функцію timeit і отримати час виконання функції hasDigit() для 100 000 запусків.

import timeit
timeit.timeit('hasDigit(thislist)',setup=setup,number=100000)
# Вивід
0.2810094920000097

Висновок

Ви навчилися використовувати функцію timeit в Python для вимірювання часу виразів, функцій та інших викликів. Це дозволить вам порівнювати продуктивність коду, час виконання різних реалізацій однієї і тієї ж функції тощо.

Підсумуємо, чому ви навчилися у цьому посібнику. Ви можете використовувати функцію timeit() зі синтаксисом timeit.timeit(stmt=…,setup=…,number=…). Крім того, можна запускати timeit з командного рядка для вимірювання часу коротких фрагментів коду.

Як наступний крок, ви можете вивчити інші інструменти профілювання Python, наприклад, line-profiler і memprofiler, для профілювання коду щодо часу та пам’яті відповідно.

Далі ви дізнаєтеся, як обчислити різницю в часі в Python.