Есть ли простой способ протестировать скрипт Python?


82

Обычно я использую команду оболочки time. Моя цель - проверить, является ли набор данных маленьким, средним, большим или очень большим, сколько времени и памяти будет использоваться.

Какие-нибудь инструменты для Linux или просто Python для этого?

Ответы:


120

Взгляните на timeit , профилировщик python и pycallgraph . Также не забудьте взглянуть на комментарий ниже,nikicc упомянув " SnakeViz ". Это дает вам еще одну визуализацию данных профилирования, которая может быть полезной.

время

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 был официально закрыт с февраля 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. Затем файл вывода может быть очень хорошо представлен в браузере с помощью программы SnakeViz, использующейsnakeviz results.prof
nikicc

последний выпуск pycallgraph был в 2013 году, и он был официально заброшен с 2018
Борис

@ Борис Это полезно знать. Я действительно использовал его вчера, и - по крайней мере, сейчас - он все еще работает. Я обновлю пост. Спасибо за информацию.
exhuma

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

это print "Function =% s, Time =% s"% (func .__ name__, t2 - t1), конечно. Спасибо, действительно удобно
user1941126 04

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 петель, и я не смотрел, как это настроить! Мне нравится, что мой код уже делится на количество повторений. Но timeit, без сомнения, лучшее решение, прошу прощения за его пренебрежение.
Сэм Уоткинс

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
Руи Андрада

6

Профилировщик памяти для всех ваших потребностей в памяти.

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

Посмотрите на нос и на один из его плагинов, этот в частности.

После установки нос - это сценарий на вашем пути, который вы можете вызвать в каталоге, который содержит некоторые сценарии 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 output, потому что они могут только общее время для каждой функции из коробки.

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

И это именно то, что snakeviz предоставляет в виде «сосульки» по умолчанию.

Сначала вам нужно сбросить данные cProfile в двоичный файл, а затем вы можете использовать snakeviz на этом

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

Это напечатает URL-адрес стандартного вывода, который вы можете открыть в своем браузере, который содержит желаемый результат, который выглядит следующим образом:

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

и тогда вы можете:

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

Более профильный вопрос: как профилировать сценарий 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.