Gibt es eine einfache Möglichkeit, Python-Skripte zu vergleichen?

82

Normalerweise benutze ich den Shell-Befehl time. Mein Zweck ist es zu testen, ob Daten klein, mittel, groß oder sehr groß sind, wie viel Zeit und Speicherplatz benötigt werden.

Irgendwelche Tools für Linux oder nur Python, um dies zu tun?

noomz
quelle

Antworten:

119

Schauen Sie sich timeit , den Python-Profiler und den Pycallgraph an . Schauen Sie sich auch den Kommentar unten an,nikicc indem Sie " SnakeViz " erwähnen . Es gibt Ihnen eine weitere Visualisierung von Profildaten, die hilfreich sein kann.

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

Im Wesentlichen können Sie den Python-Code als Zeichenfolgenparameter übergeben. Er wird in der angegebenen Häufigkeit ausgeführt und gibt die Ausführungszeit aus. Die wichtigen Teile aus den Dokumenten :

timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000, globals=None) Erstellen Sie eine TimerInstanz mit der angegebenen Anweisung, Setup - Code und Timer - Funktion und führen Sie seine timeitMethode mit Nummer Hinrichtungen. Das optionale globale Argument gibt einen Namespace an, in dem der Code ausgeführt werden soll.

... und:

Timer.timeit(number=1000000) Zeit Anzahl Ausführungen der Hauptaussage. Dadurch wird die Setup-Anweisung einmal ausgeführt und anschließend die Zeit zurückgegeben, die zum mehrmaligen Ausführen der Hauptanweisung benötigt wird, gemessen in Sekunden als Float. Das Argument ist die Häufigkeit, mit der die Schleife durchlaufen wird, standardmäßig eine Million. Die Hauptanweisung, die Setup-Anweisung und die zu verwendende Timer-Funktion werden an den Konstruktor übergeben.

Hinweis: Wird standardmäßig während des Timings timeitvorübergehend ausgeschaltet garbage collection. Der Vorteil dieses Ansatzes besteht darin, dass unabhängige Timings vergleichbarer werden. Dieser Nachteil besteht darin, dass GC ein wichtiger Bestandteil der Leistung der gemessenen Funktion sein kann. In diesem Fall kann GC als erste Anweisung in der Setup- Zeichenfolge wieder aktiviert werden . Zum Beispiel:

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

Profilerstellung

Profilierungs geben Ihnen einen viel detaillierteren Vorstellung davon , was vor sich geht. Hier ist das "sofortige Beispiel" aus den offiziellen Dokumenten :

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

Welches wird Ihnen geben:

      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)

Beide Module sollen Ihnen eine Vorstellung davon geben, wo Sie nach Engpässen suchen müssen.

profileSchauen Sie sich auch diesen Beitrag an, um die Ausgabe von in den Griff zu bekommen

Pycallgraph

Dieses Modul verwendet graphviz, um Callgraphs wie die folgenden zu erstellen:

Callgraph Beispiel

Sie können anhand der Farbe leicht erkennen, welche Pfade am häufigsten verwendet werden. Sie können sie entweder mit der Pycallgraph-API oder mit einem gepackten Skript erstellen:

pycallgraph graphviz -- ./mypythonscript.py

Der Overhead ist jedoch ziemlich beträchtlich. Bei bereits lang laufenden Prozessen kann das Erstellen des Diagramms einige Zeit dauern.

Exhuma
quelle
10
Bei Verwendung von cProfile besteht auch die Möglichkeit, das gesamte Skript zu profilieren und die Ergebnisse in einer Datei mit zu speichern python -m cProfile -o results.prof myscript.py. Die oputput-Datei kann dann in einem Browser von einem Programm namens SnakeViz unter Verwendung vonsnakeviz results.prof
nikicc
28

Ich benutze einen einfachen Dekorateur, um die Funkzeit zu messen

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
quelle
es wird natürlich "Funktion =% s, Zeit =% s"% (func .__ name__, t2 - t1) gedruckt.
Vielen Dank
16

Das timeitModul war langsam und komisch, also schrieb ich Folgendes:

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

Beispiel:

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

Für mich heißt es:

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

Dies ist eine primitive Art von Benchmarking, aber es ist gut genug.

Sam Watkins
quelle
7
@exhuma, ich vergesse die Details und war vielleicht voreilig in meiner Einschätzung! Ich denke, ich habe "seltsam" gesagt, weil zwei Codestücke als Zeichenfolgen verwendet werden (anstelle von Funktion / Lambda). Aber ich kann den Wert darin sehen, wenn sehr kurz laufende Codesegmente zeitlich festgelegt werden. Ich habe wohl "langsam" gesagt, weil standardmäßig 1.000.000 Schleifen verwendet werden, und ich habe mir nicht angesehen, wie ich das anpassen soll! Ich mag es, dass mein Code bereits durch die Anzahl der Wiederholungen geteilt wird. Aber timeit ist zweifellos eine bessere Lösung, ich entschuldige mich dafür, dass ich es abgelehnt habe.
Sam Watkins
11

Normalerweise sehe ich schnell time ./script.py, wie lange es dauert. Das zeigt Ihnen jedoch nicht den Speicher, zumindest nicht als Standard. Sie können verwenden /usr/bin/time -v ./script.py, um viele Informationen abzurufen, einschließlich der Speichernutzung.

Martin Ueding
quelle
1
Denken Sie nur daran, dass dieser Befehl /usr/bin/timemit -vOption in vielen Distributionen nicht standardmäßig verfügbar ist und installiert werden muss. sudo apt-get install timein debian, ubuntu usw. pacman -S timearchlinux
Rui Andrada
5

Memory Profiler für alle Ihre Speicheranforderungen.

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

Führen Sie eine Pip-Installation aus:

pip install memory_profiler

Importieren Sie die Bibliothek:

import memory_profiler

Fügen Sie dem Element, das Sie profilieren möchten, einen Dekorateur hinzu:

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

if __name__ == '__main__':
    my_func()

Führen Sie den Code aus:

python -m memory_profiler example.py

Erhalten Sie die Ausgabe:

 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

Beispiele stammen aus den oben verlinkten Dokumenten.

John Von Neumann
quelle
3

Schauen Sie sich die Nase und eines ihrer Plugins an, dieses insbesondere.

Nach der Installation ist Nase ein Skript in Ihrem Pfad, das Sie in einem Verzeichnis aufrufen können, das einige Python-Skripte enthält:

$: nosetests

Dadurch werden alle Python-Dateien im aktuellen Verzeichnis durchsucht und alle Funktionen ausgeführt, die als Test erkannt werden. Beispielsweise werden alle Funktionen mit dem Wort test_ im Namen als Test erkannt.

Sie können also einfach ein Python-Skript namens test_yourfunction.py erstellen und so etwas darin schreiben:

$: cat > test_yourfunction.py

def test_smallinput():
    yourfunction(smallinput)

def test_mediuminput():
    yourfunction(mediuminput)

def test_largeinput():
    yourfunction(largeinput)

Dann musst du rennen

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

Verwenden Sie zum Lesen der Profildatei diese Python-Zeile:

python -c "import hotshot.stats ; stats = hotshot.stats.load('yourstatsprofile.prof') ; stats.sort_stats('time', 'calls') ; stats.print_stats(200)"
dalloliogm
quelle
Mir scheint, dass dies dasselbe tut wie der Profiler aus der Standard-Python-Bibliothek. Testen war nicht das Thema der Frage. Plus: nosesetzt auf hotshot. Es wird seit Python 2.5 nicht mehr gepflegt und nur noch "für den speziellen Gebrauch" aufbewahrt
exhuma
2

Seien Sie vorsichtig, es timeitist sehr langsam, es dauert 12 Sekunden auf meinem mittleren Prozessor, um nur zu initialisieren (oder vielleicht die Funktion auszuführen). Sie können diese akzeptierte Antwort testen

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

Für eine einfache Sache, die ich timestattdessen verwenden werde, gibt es auf meinem PC das Ergebnis zurück0.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
quelle
1
timeitführt Ihre Funktion viele Male aus, um das Rauschen zu mitteln. Die Anzahl der Wiederholungen ist eine Option, siehe Benchmarking-Laufzeiten in Python oder den späteren Teil der akzeptierten Antwort auf diese Frage.
Peter Cordes
1

Der einfache Weg, eine Funktion schnell zu testen, ist die Verwendung dieser Syntax: %timeit my_code

Zum Beispiel :

%timeit a = 1

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

snakeviz interaktiver Viewer für cProfile

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

cProfile wurde unter https://stackoverflow.com/a/1593034/895245 erwähnt und snakeviz wurde in einem Kommentar erwähnt , aber ich wollte es weiter hervorheben.

Es ist sehr schwierig, die Programmleistung nur durch Betrachten cprofile/ Ausgeben zu debuggen pstats, da nur die Gesamtzeiten pro Funktion sofort verfügbar sind.

Im Allgemeinen müssen wir jedoch eine verschachtelte Ansicht sehen, die die Stapelspuren jedes Aufrufs enthält, um die wichtigsten Engpässe tatsächlich leicht zu finden.

Und genau das bietet snakeviz über seine Standardansicht "Eiszapfen".

Zuerst müssen Sie die cProfile-Daten in eine Binärdatei sichern, und dann können Sie darauf snakeviz

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

Dadurch wird eine URL zu stdout gedruckt, die Sie in Ihrem Browser öffnen können und die die gewünschte Ausgabe enthält, die folgendermaßen aussieht:

Geben Sie hier die Bildbeschreibung ein

und Sie können dann:

  • Bewegen Sie den Mauszeiger über jedes Feld, um den vollständigen Pfad zu der Datei anzuzeigen, die die Funktion enthält
  • Klicken Sie auf ein Feld, damit dieses Feld oben angezeigt wird, um es zu vergrößern

Profilorientiertere Frage: Wie können Sie ein Python-Skript profilieren?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
-1

Wenn Sie keinen Boilerplate-Code für die Zeit schreiben und einfach zu analysierende Ergebnisse erhalten möchten, werfen Sie einen Blick auf Benchmarkit . Außerdem wird der Verlauf früherer Läufe gespeichert, sodass die gleiche Funktion im Verlauf der Entwicklung leicht verglichen werden kann.

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

Druckt auf das Terminal und gibt eine Liste der Wörterbücher mit Daten für den letzten Lauf zurück. Befehlszeilen-Einstiegspunkte sind ebenfalls verfügbar.

Geben Sie hier die Bildbeschreibung ein

Wenn Sie sich ändern N=1000000und erneut ausführen

Geben Sie hier die Bildbeschreibung ein

v.grabovets
quelle