Czy istnieje prosty sposób na przetestowanie skryptu Pythona?

82

Zwykle używam polecenia powłoki time. Moim celem jest sprawdzenie, czy dane są małe, średnie, duże lub bardzo duże, ile zajmie czasu i pamięci.

Jakieś narzędzia dla Linuksa lub po prostu Pythona, aby to zrobić?

noomz
źródło

Odpowiedzi:

120

Spójrz na timeit , Python Profiler i Pycallgraph . Upewnij się także, aby rzucić okiem na komentarzu poniżejnikicc wspomnieć „ SnakeViz ”. Daje Ci kolejną wizualizację danych profilowania, które mogą być pomocne.

czas

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()))

Zasadniczo możesz przekazać go kod Pythona jako parametr ciągu, który będzie działał w określonej ilości razy i drukuje czas wykonania. Ważne informacje z dokumentacji :

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None) Utwórz Timerinstancję z podaną instrukcją, kodem konfiguracji i funkcją timera i uruchom jej timeitmetodę z wykonaniami liczbowymi . Opcjonalny argument globals określa przestrzeń nazw, w której ma być wykonywany kod.

... i:

Timer.timeit(number=1000000) Czas liczba egzekucji głównego rachunku. Spowoduje to jednorazowe wykonanie instrukcji setup, a następnie zwrócenie czasu potrzebnego do wykonania instrukcji main kilka razy, mierzonego w sekundach jako liczba zmiennoprzecinkowa. Argumentem jest liczba przejść przez pętlę, domyślnie jeden milion. Do konstruktora przekazywane są instrukcja main, instrukcja setup i funkcja timera, która ma być użyta.

Uwaga: domyślnie timeitwyłącza się tymczasowo garbage collectionpodczas odliczania czasu. Zaletą tego podejścia jest to, że sprawia, że ​​niezależne czasy są bardziej porównywalne. Ta wada polega na tym, że GC może być ważnym składnikiem wydajności mierzonej funkcji. Jeśli tak, GC można ponownie włączyć jako pierwszą instrukcję w ciągu konfiguracyjnym . Na przykład:

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

Profilowy

Profilowanie da ci dużo bardziej szczegółowy obraz tego, co się dzieje. Oto „natychmiastowy przykład” z oficjalnych dokumentów :

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

Co da ci:

      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)

Oba te moduły powinny dać ci wyobrażenie o tym, gdzie szukać wąskich gardeł.

Aby profilezapoznać się z wynikami działania programu , zajrzyj do tego postu

pycallgraph

UWAGA pycallgraph został oficjalnie porzucony od lutego 2018 roku . Jednak od grudnia 2020 r. Nadal działał na Pythonie 3.6. Dopóki nie ma żadnych podstawowych zmian w sposobie, w jaki Python ujawnia API do profilowania, powinien pozostać pomocnym narzędziem.

Ten moduł używa graphviz do tworzenia callgraphów, takich jak:

przykład callgraph

Możesz łatwo sprawdzić, które ścieżki zajęły najwięcej czasu według koloru. Możesz je utworzyć za pomocą pycallgraph API lub za pomocą skryptu w pakiecie:

pycallgraph graphviz -- ./mypythonscript.py

Jednak koszty ogólne są dość znaczne. Tak więc w przypadku już długotrwałych procesów tworzenie wykresu może zająć trochę czasu.

exhuma
źródło
10
W przypadku korzystania z cProfile istnieje również opcja profilowania całego skryptu i zapisywania wyników do pliku z rozszerzeniem python -m cProfile -o results.prof myscript.py. Plik oputput można wówczas bardzo ładnie przedstawione w przeglądarce za pomocą programu o nazwie SnakeViz użyciusnakeviz results.prof
nikicc
Ostatnie wydanie pycallgraph miało miejsce w 2013 roku i zostało oficjalnie porzucone od 2018 roku
Boris
@Boris Dobrze wiedzieć. Właściwie użyłem go wczoraj i - przynajmniej na razie - nadal działa. Zaktualizuję post. Thatnk you for the info.
ekshuma
28

Używam prostego dekoratora do synchronizacji funkcji

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
Danyun Liu
źródło
jest to oczywiście print "Funkcja =% s, Czas =% s"% (func .__ nazwa__, t2 - t1) oczywiście. Dzięki, naprawdę wygodne
user1941126
17

timeitModuł była powolna i dziwne, więc napisałem tak:

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

Przykład:

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

Dla mnie mówi:

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

To prymitywny rodzaj testów porównawczych, ale wystarczająco dobry.

Sam Watkins
źródło
7
@exhuma, zapomniałem o szczegółach i być może dokonałem pochopnej oceny! Myślę, że powiedziałem „dziwne”, ponieważ przyjmuje dwa fragmenty kodu jako ciągi znaków (zamiast funkcji / lambda). Ale widzę wartość w tym przy synchronizowaniu bardzo krótko działających segmentów kodu. Wydaje mi się, że powiedziałem „wolno”, ponieważ domyślnie jest to 1 000 000 pętli i nie zastanawiałem się, jak to zmienić! Podoba mi się, że mój kod jest już podzielony przez liczbę powtórzeń. Ale czas to bez wątpienia lepsze rozwiązanie, przepraszam za odrzucenie tego.
Sam Watkins
11

Zwykle szybko time ./script.pysprawdzam, jak długo to potrwa. To jednak nie pokazuje pamięci, przynajmniej nie domyślnie. Możesz użyć, /usr/bin/time -v ./script.pyaby uzyskać wiele informacji, w tym użycie pamięci.

Martin Ueding
źródło
1
tylko pamiętaj, to polecenie /usr/bin/timez -vopcją nie jest domyślnie dostępne w wielu dystrybucjach, musi zostać zainstalowane. sudo apt-get install timew debianie, ubuntu itp. pacman -S timearchlinux
Rui Andrada
6

Memory Profiler dla wszystkich Twoich potrzeb związanych z pamięcią.

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

Uruchom instalację pip:

pip install memory_profiler

Importuj bibliotekę:

import memory_profiler

Dodaj dekoratora do przedmiotu, który chcesz profilować:

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

if __name__ == '__main__':
    my_func()

Wykonaj kod:

python -m memory_profiler example.py

Odzyskaj dane wyjściowe:

 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

Przykłady pochodzą z dokumentów, do których linki znajdują się powyżej.

John Von Neumann
źródło
3

Wystarczy popatrzeć na nosie i na jednym ze swoich wtyczek, ten jeden w szczególności.

Po zainstalowaniu nos jest skryptem na twojej ścieżce i możesz go wywołać w katalogu zawierającym skrypty Pythona:

$: nosetests

Spowoduje to przeszukanie wszystkich plików Pythona w bieżącym katalogu i wykonanie dowolnej funkcji, którą rozpozna jako test: na przykład, jako test rozpoznaje dowolną funkcję ze słowem test_ w nazwie.

Możesz więc po prostu utworzyć skrypt Pythona o nazwie test_yourfunction.py i napisać w nim coś takiego:

$: cat > test_yourfunction.py

def test_smallinput():
    yourfunction(smallinput)

def test_mediuminput():
    yourfunction(mediuminput)

def test_largeinput():
    yourfunction(largeinput)

Wtedy musisz biec

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

i aby odczytać plik profilu, użyj tej linii Pythona:

python -c "import hotshot.stats ; stats = hotshot.stats.load('yourstatsprofile.prof') ; stats.sort_stats('time', 'calls') ; stats.print_stats(200)"
dalloliogm
źródło
Wydaje mi się, że działa to tak samo, jak profiler ze standardowej biblioteki Pythona. Testowanie nie było tematem pytania. Plus: nosepolega na hotshot. Nie jest już obsługiwany od Pythona 2.5 i jest przechowywany tylko „do użytku specjalistycznego”
exhuma
2

Uważaj, timeitjest bardzo powolny, po prostu zainicjowanie (lub może uruchomienie funkcji) zajmuje mojemu średniemu procesorowi 12 sekund. możesz przetestować tę zaakceptowaną odpowiedź

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

timezamiast tego użyję prostej rzeczy , na moim komputerze zwróci wynik0.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
cieunteung
źródło
1
timeituruchamia twoją funkcję wiele razy, aby uśrednić szum. Liczba powtórzeń jest opcją, zobacz Benchmarking runtime w Pythonie lub późniejszą część zaakceptowanej odpowiedzi na to pytanie.
Peter Cordes
1

Prostym sposobem szybkiego przetestowania dowolnej funkcji jest użycie następującej składni: %timeit my_code

Na przykład :

%timeit a = 1

13.4 ns ± 0.781 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
Arcyno
źródło
1

snakeviz interaktywna przeglądarka dla cProfile

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

cProfile został wymieniony na https://stackoverflow.com/a/1593034/895245, a snakeviz został wymieniony w komentarzu , ale chciałem to jeszcze bardziej podkreślić.

Bardzo trudno jest debugować wydajność programu po prostu patrząc na cprofile/ pstatswyjście, ponieważ mogą one tylko sumaryczne czasy na funkcję po wyjęciu z pudełka.

Jednak generalnie potrzebujemy zobaczyć zagnieżdżony widok zawierający ślady stosu każdego wywołania, aby faktycznie łatwo znaleźć główne wąskie gardła.

I to jest dokładnie to, co zapewnia snakeviz w swoim domyślnym widoku „sopel lodu”.

Najpierw musisz zrzucić dane cProfile do pliku binarnego, a następnie możesz na tym snakevizować

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

Spowoduje to wydrukowanie adresu URL do standardowego wyjścia, które można otworzyć w przeglądarce, zawierającego żądane dane wyjściowe, które wyglądają następująco:

wprowadź opis obrazu tutaj

i możesz wtedy:

  • najedź kursorem na każde pole, aby zobaczyć pełną ścieżkę do pliku zawierającego funkcję
  • kliknij pole, aby pojawiło się ono na górze jako sposób na powiększenie

Pytanie bardziej zorientowane na profil: Jak można profilować skrypt w Pythonie?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
-1

Jeśli nie chcesz pisać standardowego kodu na czas i łatwo analizować wyniki, zapoznaj się z benchmarkit . Zapisuje również historię poprzednich przebiegów, więc łatwo jest porównać tę samą funkcję w trakcie rozwoju.

# 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',
)  

Wyświetla na terminalu i zwraca listę słowników z danymi z ostatniego uruchomienia. Dostępne są również punkty wejścia wiersza poleceń.

wprowadź opis obrazu tutaj

Jeśli zmienisz N=1000000i uruchomisz ponownie

wprowadź opis obrazu tutaj

v.grabovets
źródło