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ć?

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.


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

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.


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.


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.


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

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

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)

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?


-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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.