Профілювання коду Python за допомогою timeit і cProfile - KDnuggets

Профілювання коду Python за допомогою timeit і cProfile – KDnuggets

Вихідний вузол: 2874649

Профілювання коду Python за допомогою timeit і cProfile
Зображення автора
 

Як розробник програмного забезпечення, ви, ймовірно, чули цю цитату «Передчасна оптимізація – корінь усіх зол»—не раз—у своїй кар’єрі. Хоча оптимізація може бути не дуже корисною (або абсолютно необхідною) для невеликих проектів, профілювання часто корисне. 

Після того, як ви завершите кодування модуля, доцільно сформулювати свій код, щоб виміряти, скільки часу потрібно для виконання кожного розділу. Це може допомогти визначити запахи коду та скерувати оптимізацію для покращення якості коду. Тому завжди профілюйте свій код перед оптимізацією!

Щоб зробити перші кроки, цей посібник допоможе вам почати роботу з профілюванням у Python за допомогою вбудованого timeit та cПрофіль модулі. Ви навчитеся використовувати як інтерфейс командного рядка, так і еквівалентні виклики в сценаріях Python.

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

Розглянемо простий приклад перевертання списку Python. Ми виміряємо час виконання отримання зворотної копії списку за допомогою:

  • reversed() функція, і
  • нарізка списку. 
>>> nums=[6,9,2,3,7]
>>> list(reversed(nums))
[7, 3, 2, 9, 6]
>>> nums[::-1]
[7, 3, 2, 9, 6]

 

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

Можна бігати timeit у командному рядку за допомогою синтаксису:

$ python -m timeit -s 'setup-code' -n 'number' -r 'repeat' 'stmt'

 

Ви повинні надати заяву stmt час виконання якого необхідно виміряти. 

Ви можете вказати setup кодувати, коли це необхідно, використовуючи короткий параметр -s або довгий параметр –setup. Код налаштування буде запущено лише один раз.

Команда number разів, щоб виконати оператор: короткий параметр -n або довгий параметр –число є необов’язковими. І кількість повторів цього циклу: коротка опція -r або довга опція –repeat також необов’язкові.

Давайте подивимося на те, що описано вище, у дії для нашого прикладу:

Ось створення списку setup код і перевертання списку є оператором, який має бути хронометрований:

$ python -m timeit -s 'nums=[6,9,2,3,7]' 'list(reversed(nums))'
500000 loops, best of 5: 695 nsec per loop

 

Якщо ви не вказуєте значення для repeat, використовується значення за замовчуванням 5. А коли не вказуєш number, код виконується стільки разів, скільки потрібно, щоб досягти загального часу принаймні 0.2 секунд.

Цей приклад явно встановлює кількість разів для виконання оператора:

$ python -m timeit -s 'nums=[6,9,2,3,7]' -n 100Bu000 'list(reversed(nums))'
100000 loops, best of 5: 540 nsec per loop

 

Значення за замовчуванням repeat дорівнює 5, але ми можемо встановити будь-яке відповідне значення:

$ python3 -m timeit -s 'nums=[6,9,2,3,7]' -r 3 'list(reversed(nums))'
500000 loops, best of 3: 663 nsec per loop

 

Давайте також розрахуємо підхід до нарізки списку:

$ python3 -m timeit -s 'nums=[6,9,2,3,7]' 'nums[::-1]'
1000000 loops, best of 5: 142 nsec per loop

 

Підхід до нарізання списку здається швидшим (усі приклади є в Python 3.10 на Ubuntu 22.04).

Виконання timeit у сценарії Python

Ось еквівалент виконання timeit у сценарії Python:

import timeit setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]' t1 = timeit.timeit(setup=setup,stmt=stmt1,number=number)
t2 = timeit.timeit(setup=setup,stmt=stmt2,number=number) print(f"Using reversed() fn.: {t1}")
print(f"Using list slicing: {t2}")

 

Команда timeit() callable повертає час виконання stmt та цінності number разів. Зверніть увагу, що ми можемо явно вказати кількість разів, які потрібно запустити, або зробити number прийняти значення за замовчуванням 1000000.

Output >>
Using reversed() fn.: 0.08982690000000002
Using list slicing: 0.015550800000000004

 

Це запускає оператор — без повторення функції таймера — для вказаного number разів і повертає час виконання. Також досить поширений у використанні time.repeat() і візьміть мінімальний час, як показано:

import timeit setup = 'nums=[9,2,3,7,6]'
number = 100000
stmt1 = 'list(reversed(nums))'
stmt2 = 'nums[::-1]' t1 = min(timeit.repeat(setup=setup,stmt=stmt1,number=number))
t2 = min(timeit.repeat(setup=setup,stmt=stmt2,number=number)) print(f"Using reversed() fn.: {t1}")
print(f"Using list slicing: {t2}")

 

Це повторить процес запуску коду number разів repeat кількість разів і повертає мінімальний час виконання. Тут ми маємо 5 повторень по 100000 XNUMX разів кожне.

Output >>
Using reversed() fn.: 0.055375300000000016
Using list slicing: 0.015101400000000043

Ми побачили, як timeit можна використовувати для вимірювання часу виконання коротких фрагментів коду. Однак на практиці корисніше профілювати весь сценарій Python. 

Це дасть нам час виконання всіх функцій і викликів методів, включаючи вбудовані функції та методи. Тож ми можемо отримати краще уявлення про дорожчі виклики функцій і визначити можливості для оптимізації. Наприклад: може бути надто повільний виклик API. Або функція може мати цикл, який можна замінити більш пітонічним виразом розуміння. 

Давайте навчимося профілювати скрипти Python за допомогою модуля cProfile (також частина стандартної бібліотеки Python). 

Розглянемо такий сценарій Python:

# main.py
import time def func(num): for i in range(num): print(i) def another_func(num): time.sleep(num) print(f"Slept for {num} seconds") def useful_func(nums, target): if target in nums: return nums.index(target) if __name__ == "__main__": func(1000) another_func(20) useful_func([2, 8, 12, 4], 12)

 

Тут ми маємо три функції:

  • func() який переглядає діапазон чисел і друкує їх.
  • another func() який містить виклик до sleep() функції.
  • useful_func() який повертає індекс цільового номера в списку (якщо ціль присутній у списку).

Перелічені вище функції будуть викликані кожного разу, коли ви запускатимете сценарій main.py.

Запуск cProfile у командному рядку

Запустіть cProfile у командному рядку за допомогою:

python3 -m file-name.py

 

Тут ми назвали файл main.py:

python3 -m main.py

 

Запуск цього повинен дати вам наступний результат:

 Output >> 0 ... 999 Slept for 20 seconds

 

І наступний профіль:

 

Профілювання коду Python за допомогою timeit і cProfile
 

Тут, ncalls відноситься до кількості викликів функції і percall відноситься до часу на виклик функції. Якщо значення ncalls тоді більше за одиницю percall це середній час усіх викликів.

Переважає час виконання сценарію another_func який використовує вбудований sleep виклик функції (спить на 20 секунд). Ми це бачимо print виклики функцій також досить дорогі. 

Використання cProfile у сценарії Python

Хоча запуск cProfile у командному рядку працює нормально, ви також можете додати функцію профілювання до сценарію Python. Ви можете використовувати cProfile у поєднанні з модуль pstats для профілювання та доступу до статистики.

Як найкраща практика для кращої роботи з налаштуванням ресурсу та демонтажем, використовуйте оператор with і створіть об’єкт профілю, який використовуватиметься як менеджер контексту:

# main.py
import pstats
import time
import cProfile def func(num): for i in range(num): print(i) def another_func(num): time.sleep(num) print(f"Slept for {num} seconds") def useful_func(nums, target): if target in nums: return nums.index(target) if __name__ == "__main__": with cProfile.Profile() as profile: func(1000) another_func(20) useful_func([2, 8, 12, 4], 12) profile_result = pstats.Stats(profile) profile_result.print_stats()

 

Давайте детальніше розглянемо створений вихідний профіль:

 

Профілювання коду Python за допомогою timeit і cProfile
 

Коли ви профілюєте великий сценарій, це буде корисно сортувати результати за часом виконання. Для цього можна зателефонувати sort_stats на об’єкт профілю та сортування на основі часу виконання: 

...
if __name__ == "__main__": with cProfile.Profile() as profile: func(1000) another_func(20) useful_func([2, 8, 12, 4], 12) profile_result = pstats.Stats(profile) profile_result.sort_stats(pstats.SortKey.TIME) profile_result.print_stats()

 

Коли ви зараз запустите сценарій, ви зможете побачити результати, відсортовані за часом:

 

Профілювання коду Python за допомогою timeit і cProfile

Сподіваюся, цей посібник допоможе вам почати роботу з профілюванням у Python. Завжди пам’ятайте, що оптимізація ніколи не повинна відбуватися за рахунок читабельності. Якщо вам цікаво дізнатися про інші профайлери, включно зі сторонніми пакетами Python, перегляньте це стаття про профайлери Python.
 
 
Бала Прія С є розробником і технічним автором з Індії. Їй подобається працювати на стику математики, програмування, науки про дані та створення контенту. Сфери її інтересів і знань включають DevOps, науку про дані та обробку природної мови. Вона любить читати, писати, кодувати та кави! Зараз вона навчається та ділиться своїми знаннями зі спільнотою розробників, створюючи навчальні посібники, інструкції, думки тощо.
 

Часова мітка:

Більше від KDnuggets