Чи існує простий спосіб порівняти скрипт python?


82

Зазвичай я використовую команду shell time. Моя мета - перевірити, чи є дані малими, середніми, великими чи дуже великими, скільки часу та пам'яті буде використано.

Будь-які інструменти для Linux або просто python для цього?

Відповіді:


120

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

timeit

def test():
    """Stupid test function"""
    lst = []
    for i in range(100):
        lst.append(i)

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test"))

    # For Python>=3.5 one can also write:
    print(timeit.timeit("test()", globals=locals()))

По суті, ви можете передати йому код python як параметр рядка, і він буде виконуватися в зазначену кількість разів і друкуватиме час виконання. Важливі фрагменти з документів :

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None) Створіть Timerекземпляр із заданим оператором, кодом настройки та функцією таймера та запустіть його timeitметод із виконанням чисел . Необов'язковий аргумент globals визначає простір імен, в якому слід виконувати код.

... і:

Timer.timeit(number=1000000)Виконання часового числа основного висловлення. Це виконує оператор установки один раз, а потім повертає час, необхідний для виконання основного оператора кілька разів, вимірюється секундами як плаваючий. Аргументом є кількість разів через цикл, за замовчуванням один мільйон. Основний оператор, інструкція про встановлення та функція таймера, що використовуватимуться, передаються конструктору.

Примітка: За замовчуванням timeitтимчасово вимикається garbage collectionпід час синхронізації. Перевага цього підходу полягає в тому, що він робить незалежні терміни більш порівнянними. Цей недолік полягає в тому, що ГХ може бути важливим компонентом роботи вимірюваної функції. Якщо так, то GC можна знову активувати як перший оператор у рядку налаштування . Наприклад:

timeit.Timer('for i in xrange(10): oct(i)', 'gc.enable()').timeit()

Профілювання

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

import cProfile
import re
cProfile.run('re.compile("foo|bar")')

Що дасть вам:

      197 function calls (192 primitive calls) in 0.002 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.001    0.001 <string>:1(<module>)
     1    0.000    0.000    0.001    0.001 re.py:212(compile)
     1    0.000    0.000    0.001    0.001 re.py:268(_compile)
     1    0.000    0.000    0.000    0.000 sre_compile.py:172(_compile_charset)
     1    0.000    0.000    0.000    0.000 sre_compile.py:201(_optimize_charset)
     4    0.000    0.000    0.000    0.000 sre_compile.py:25(_identityfunction)
   3/1    0.000    0.000    0.000    0.000 sre_compile.py:33(_compile)

Обидва ці модулі повинні дати вам уявлення про те, де шукати вузькі місця.

Крім того, щоб зрозуміти результати profile, перегляньте цю публікацію

pycallgraph

ПРИМІТКА. Pycallgraph офіційно покинутий з лютого 2018 року . Станом на грудень 2020 року він все ще працював над Python 3.6. Поки не буде основних змін у тому, як python виставляє API профілювання, він повинен залишатися корисним інструментом.

Цей модуль використовує graphviz для створення графіків, таких як:

приклад callgraph

Ви можете легко побачити, які шляхи витратили найбільше часу за кольором. Ви можете створити їх за допомогою API pycallgraph, або за допомогою пакуваного сценарію:

pycallgraph graphviz -- ./mypythonscript.py

Накладні витрати досить значні. Отже, для вже тривалих процесів створення графіку може зайняти деякий час.


10
Якщо використовується cProfile, є також можливість профілювати весь сценарій та зберігати результати у файлі за допомогою python -m cProfile -o results.prof myscript.py. Потім файл oputput може бути дуже приємно представлений у браузері програмою під назвою SnakeViz за допомогоюsnakeviz results.prof
nikicc

Останній реліз pycallgraph був у 2013 році, і офіційно від нього відмовились з 2018 року
Борис

@Boris Це добре знати. Я фактично використовував його вчора, і - принаймні наразі - він все ще працює. Я оновлю пост. Це вам за інформацію.
ексхума

28

Я використовую простий декоратор, щоб визначити час

def st_time(func):
    """
        st decorator to calculate the total time of a func
    """

    def st_func(*args, **keyArgs):
        t1 = time.time()
        r = func(*args, **keyArgs)
        t2 = time.time()
        print "Function=%s, Time=%s" % (func.__name__, t2 - t1)
        return r

    return st_func

це друк "Функція =% s, Час =% s"% (func .__ name__, t2 - t1), звичайно. Подяка, дійсно зручно
user1941126

17

timeitМодуль був повільним і дивно, тому я написав це:

def timereps(reps, func):
    from time import time
    start = time()
    for i in range(0, reps):
        func()
    end = time()
    return (end - start) / reps

Приклад:

import os
listdir_time = timereps(10000, lambda: os.listdir('/'))
print "python can do %d os.listdir('/') per second" % (1 / listdir_time)

Для мене це говорить:

python can do 40925 os.listdir('/') per second

Це примітивний різновид бенчмаркінгу, але він досить хороший.


7
@exhuma, я забуваю подробиці і, можливо, поспішив з моєю оцінкою! Думаю, я сказав "дивно", оскільки для цього потрібні два фрагменти коду як рядки (а не функція / лямбда). Але я бачу цінність у цьому, коли синхронізуємо дуже короткі сегменти коду. Напевно, я сказав "повільно", оскільки за замовчуванням це 1 000 000 циклів, і я не дивився, як це налаштувати! Мені подобається, що мій код ділиться на кількість повторень. Але час, безсумнівно, є кращим рішенням.
Sam Watkins

11

Зазвичай я швидко роблю, time ./script.pyщоб побачити, скільки часу це займає. Однак це не показує вам пам'ять, принаймні не за замовчуванням. Ви можете використовувати /usr/bin/time -v ./script.pyбагато інформації, включаючи використання пам'яті.


1
пам’ятайте, ця команда /usr/bin/timeз -vопцією недоступна за замовчуванням у багатьох дистрибутивах, її потрібно встановити. sudo apt-get install timeу debian, ubuntu тощо. pacman -S timearchlinux
Rui Andrada

6

Memory Profiler для всіх ваших потреб у пам’яті.

https://pypi.python.org/pypi/memory_profiler

Запустіть установку pip:

pip install memory_profiler

Імпортуйте бібліотеку:

import memory_profiler

Додайте декоратора до предмета, який ви хочете профілі:

@profile
def my_func():
    a = [1] * (10 ** 6)
    b = [2] * (2 * 10 ** 7)
    del b
    return a

if __name__ == '__main__':
    my_func()

Виконайте код:

python -m memory_profiler example.py

Отримати результат:

 Line #    Mem usage  Increment   Line Contents
 ==============================================
 3                           @profile
 4      5.97 MB    0.00 MB   def my_func():
 5     13.61 MB    7.64 MB       a = [1] * (10 ** 6)
 6    166.20 MB  152.59 MB       b = [2] * (2 * 10 ** 7)
 7     13.61 MB -152.59 MB       del b
 8     13.61 MB    0.00 MB       return a

Приклади наведені у документах, зв’язаних вище.


3

Погляньте на ніс та на один із його плагінів, цей зокрема.

Після встановлення, nose - це скрипт на вашому шляху, і ви можете викликати його в каталог, який містить кілька сценаріїв python:

$: nosetests

Це буде виглядати у всіх файлах python у поточному каталозі та виконуватиме будь-яку функцію, яку він розпізнає як тест: наприклад, він розпізнає будь-яку функцію зі словом test_ у своєму назві як тест.

Тож ви можете просто створити скрипт python з назвою test_yourfunction.py і написати в ньому щось подібне:

$: cat > test_yourfunction.py

def test_smallinput():
    yourfunction(smallinput)

def test_mediuminput():
    yourfunction(mediuminput)

def test_largeinput():
    yourfunction(largeinput)

Тоді треба бігти

$: nosetest --with-profile --profile-stats-file yourstatsprofile.prof testyourfunction.py

і щоб прочитати файл профілю, використовуйте цей рядок python:

python -c "import hotshot.stats ; stats = hotshot.stats.load('yourstatsprofile.prof') ; stats.sort_stats('time', 'calls') ; stats.print_stats(200)"

Мені здається, що це робить те саме, що і профайлер зі стандартної бібліотеки python. Тестування не було темою питання. Плюс: noseпокладається на гарячий знімок. Він більше не підтримується з Python 2.5 і зберігається лише "для спеціалізованого використання"
exhuma

2

Будьте обережні, timeitце дуже повільно, на моєму середньому процесорі потрібно лише 12 секунд, щоб просто ініціалізувати (або, можливо, запустити функцію). Ви можете перевірити цю прийняту відповідь

def test():
    lst = []
    for i in range(100):
        lst.append(i)

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("test()", setup="from __main__ import test")) # 12 second

для простої речі я використаю timeзамість цього, на моєму ПК це поверне результат0.0

import time

def test():
    lst = []
    for i in range(100):
        lst.append(i)

t1 = time.time()

test()

result = time.time() - t1
print(result) # 0.000000xxxx

1
timeitзапускає вашу функцію багато разів, щоб усереднити шум. Кількість повторень є опцією, див. Час роботи порівняльного тестування в python або пізнішу частину прийнятої відповіді на це питання.
Пітер Кордес,

1

Найпростіший спосіб швидкого тестування будь-якої функції - використовувати такий синтаксис: %timeit my_code

Наприклад :

%timeit a = 1

13.4 ns ± 0.781 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)

1

snakeviz інтерактивний переглядач для cProfile

https://github.com/jiffyclub/snakeviz/

cProfile згадувалося на https://stackoverflow.com/a/1593034/895245, а snakeviz згадувалось у коментарі , але я хотів би виділити його далі.

Дуже важко налагодити продуктивність програми, просто подивившись на cprofile/pstats вивід, оскільки вони можуть складати лише загальну кількість разів для кожної функції.

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

І це саме те, що пропонує snakeviz за допомогою типового подання "бурулька".

Спочатку вам потрібно скинути дані cProfile у двійковий файл, а потім ви можете на цьому зміг

pip install -u snakeviz
python -m cProfile -o results.prof myscript.py
snakeviz results.prof

Це друкує URL-адресу на stdout, яку ви можете відкрити у своєму браузері, яка містить бажаний результат, який виглядає так:

введіть тут опис зображення

і ви можете:

  • наведіть курсор на кожне поле, щоб побачити повний шлях до файлу, що містить функцію
  • клацніть на коробці, щоб це вікно відображалося зверху як спосіб збільшення

Більш орієнтоване на профіль запитання: Як ви можете профілювати скрипт Python?


-1

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

# pip install benchmarkit

from benchmarkit import benchmark, benchmark_run

N = 10000
seq_list = list(range(N))
seq_set = set(range(N))

SAVE_PATH = '/tmp/benchmark_time.jsonl'

@benchmark(num_iters=100, save_params=True)
def search_in_list(num_items=N):
    return num_items - 1 in seq_list

@benchmark(num_iters=100, save_params=True)
def search_in_set(num_items=N):
    return num_items - 1 in seq_set

benchmark_results = benchmark_run(
   [search_in_list, search_in_set],
   SAVE_PATH,
   comment='initial benchmark search',
)  

Друкує на терміналі та повертає список словників із даними для останнього запуску. Також доступні точки входу в командному рядку.

введіть тут опис зображення

Якщо змінити N=1000000і повторити

введіть тут опис зображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.