Wie kann ich ein Codesegment zum Testen der Leistung mit Pythons timeit zeitlich festlegen?


160

Ich habe ein Python-Skript, das genau so funktioniert, wie es sollte, aber ich muss die Ausführungszeit schreiben. Ich habe gegoogelt, dass ich es verwenden sollte, timeitaber ich kann es scheinbar nicht zum Laufen bringen.

Mein Python-Skript sieht folgendermaßen aus:

import sys
import getopt
import timeit
import random
import os
import re
import ibm_db
import time
from string import maketrans
myfile = open("results_update.txt", "a")

for r in range(100):
    rannumber = random.randint(0, 100)

    update = "update TABLE set val = %i where MyCount >= '2010' and MyCount < '2012' and number = '250'" % rannumber
    #print rannumber

    conn = ibm_db.pconnect("dsn=myDB","usrname","secretPWD")

for r in range(5):
    print "Run %s\n" % r        
    ibm_db.execute(query_stmt)
 query_stmt = ibm_db.prepare(conn, update)

myfile.close()
ibm_db.close(conn)

Was ich brauche, ist die Zeit, die benötigt wird, um die Abfrage auszuführen und in die Datei zu schreiben results_update.txt. Der Zweck besteht darin, eine Aktualisierungsanweisung für meine Datenbank mit verschiedenen Indizes und Optimierungsmechanismen zu testen.


War / ist Ihre Frage spezifisch timeit? Ich denke nicht. In diesem Fall sollten Sie wahrscheinlich "with Pythons timeit" aus dem Titel entfernen.
Martin Thoma

Antworten:


274

Sie können time.time()oder time.clock()vor und nach dem Block verwenden, den Sie zeitlich festlegen möchten.

import time

t0 = time.time()
code_block
t1 = time.time()

total = t1-t0

Diese Methode ist nicht so genau wie timeit(es werden nicht mehrere Läufe gemittelt), aber sie ist unkompliziert.

time.time()(unter Windows und Linux) und time.clock()(unter Linux) sind für schnelle Funktionen nicht präzise genug (Sie erhalten total = 0). In diesem Fall oder wenn Sie die Zeit abgelaufen ist nach mehreren Durchläufen mitteln wollen, müssen Sie manuell die Funktion mehrmals aufrufen (wie ich denke , schon in Ihnen Beispielcode Sie tun und timeit tut automatisch , wenn Sie seine Set - Nummer Argument)

import time

def myfast():
   code

n = 10000
t0 = time.time()
for i in range(n): myfast()
t1 = time.time()

total_n = t1-t0

In Windows hat, wie Corey im Kommentar angegeben hat, time.clock()eine viel höhere Genauigkeit (Mikrosekunde statt Sekunde) und wird gegenüber bevorzugt time.time().


8
Verwenden Sie unter Windows time.clock () anstelle von time.time ()
Corey Goldberg,

4
Danke Corey, warum? weil die Uhr präziser ist (Mikrosekunden) oder gibt es etwas mehr?
Joaquin

11
Sie können timeit.default_timer () verwenden, um Ihre Codeplattform unabhängig zu machen. Je nach Betriebssystem wird entweder time.clock () oder time.time () zurückgegeben.
Marc Stober

6
Anstatt eine Uhr von Hand auszuwählen, verwenden Sie timeit.default_timer; Python hat die Arbeit bereits für Sie erledigt. Aber wirklich, Sie sollten timeit.timeit(myfast, number=n)das sich wiederholende Aufrufrad verwenden, anstatt es neu zu erfinden (und die Tatsache übersehen, dass timeitder Garbage Collector deaktiviert wird, während der Code wiederholt ausgeführt wird).
Martijn Pieters

15
update: time.clock () ist jetzt veraltet. Sie sollten jetzt time.time () verwenden. Tatsächlich wäre seit Version 3.3 die beste Option time.perf_counter ()
Madlozoz

42

Wenn Sie Ihren Code profilieren und IPython verwenden können, hat er die magische Funktion %timeit.

%%timeit arbeitet auf Zellen.

In [2]: %timeit cos(3.14)
10000000 loops, best of 3: 160 ns per loop

In [3]: %%timeit
   ...: cos(3.14)
   ...: x = 2 + 3
   ...: 
10000000 loops, best of 3: 196 ns per loop

36

Abgesehen vom Timing ist dieser Code, den Sie anzeigen, einfach falsch: Sie führen 100 Verbindungen aus (wobei alle bis auf die letzte vollständig ignoriert werden), und wenn Sie den ersten Ausführungsaufruf ausführen, übergeben Sie ihm eine lokale Variable, query_stmtdie Sie erst nach der Ausführung initialisieren Anruf.

Stellen Sie zunächst Ihren Code korrekt ein, ohne sich noch Gedanken über das Timing zu machen: dh eine Funktion, die eine Verbindung herstellt oder empfängt und 100 oder 500 oder eine beliebige Anzahl von Aktualisierungen für diese Verbindung ausführt, schließt dann die Verbindung. Sobald Sie Ihren Code richtig funktionieren lassen, ist der richtige Punkt, an dem Sie darüber nachdenken sollten, timeitihn zu verwenden!

Insbesondere wenn die Funktion, die foobarSie zeitlich festlegen möchten, eine parameterlose Funktion ist, die aufgerufen wird , können Sie timeit.timeit verwenden (2.6 oder höher - es ist in 2.5 und früher komplizierter):

timeit.timeit('foobar()', number=1000)

Sie sollten die Anzahl der Läufe besser angeben, da der Standardwert von einer Million für Ihren Anwendungsfall möglicherweise hoch ist (was dazu führt, dass Sie viel Zeit in diesem Code verbringen ;-).


26
Nachdem ich in den letzten Minuten damit zu kämpfen hatte, möchte ich zukünftige Zuschauer wissen lassen, dass Sie wahrscheinlich auch eine Setup-Variable übergeben möchten, wenn sich Ihre Funktion foobarin einer Hauptdatei befindet. So: timeit.timeit('foobar()','from __main__ import foobar',number=1000)
Rich

3
In Python 2.7.8 können Sie einfachtimeit.timeit( foobar, number=1000 )

9

Konzentrieren Sie sich auf eine bestimmte Sache . Die Festplatten-E / A ist langsam, daher würde ich das aus dem Test herausnehmen, wenn Sie nur die Datenbankabfrage optimieren möchten.

Wenn Sie die Ausführung Ihrer Datenbank zeitlich festlegen müssen, suchen Sie stattdessen nach Datenbanktools, z. B. nach dem Abfrageplan, und beachten Sie, dass die Leistung nicht nur von der genauen Abfrage und den vorhandenen Indizes abhängt, sondern auch von der Datenlast (wie viele Daten) Sie gespeichert haben).

Das heißt, Sie können einfach Ihren Code in eine Funktion einfügen und diese Funktion ausführen mit timeit.timeit():

def function_to_repeat():
    # ...

duration = timeit.timeit(function_to_repeat, number=1000)

Dies würde die Speicherbereinigung deaktivieren, die function_to_repeat()Funktion wiederholt aufrufen und die Gesamtdauer dieser Aufrufe zeitlich festlegen. Dies timeit.default_timer()ist die genaueste verfügbare Uhr für Ihre spezifische Plattform.

Sie sollten den Setup-Code aus der wiederholten Funktion entfernen . Sie sollten beispielsweise zuerst eine Verbindung zur Datenbank herstellen und dann nur die Abfragen zeitlich festlegen. Verwenden Sie diesetup Argument, um diese Abhängigkeiten entweder zu importieren oder zu erstellen, und übergeben Sie sie an Ihre Funktion:

def function_to_repeat(var1, var2):
    # ...

duration = timeit.timeit(
    'function_to_repeat(var1, var2)',
    'from __main__ import function_to_repeat, var1, var2', 
    number=1000)

würde die Globals greifen function_to_repeat, var1und var2von Ihrem Skript und diejenigen , an die Funktion jeder Wiederholung passieren.


Das Einfügen des Codes in eine Funktion ist ein Schritt, nach dem ich gesucht habe - da Code einfach zu einem String gemacht wird und evaling nicht für etwas fliegen wird, das nicht ganz trivial ist. thx
javadba

2

Ich sehe, dass die Frage bereits beantwortet wurde, möchte aber trotzdem meine 2 Cent dafür hinzufügen.

Ich habe auch ein ähnliches Szenario erlebt, in dem ich die Ausführungszeiten für verschiedene Ansätze testen und daher ein kleines Skript schreiben muss, das die Zeit für alle darin geschriebenen Funktionen aufruft.

Das Skript ist auch als Github Kern verfügbar hier .

Hoffe es wird dir und anderen helfen.

from random import random
import types

def list_without_comprehension():
    l = []
    for i in xrange(1000):
        l.append(int(random()*100 % 100))
    return l

def list_with_comprehension():
    # 1K random numbers between 0 to 100
    l = [int(random()*100 % 100) for _ in xrange(1000)]
    return l


# operations on list_without_comprehension
def sort_list_without_comprehension():
    list_without_comprehension().sort()

def reverse_sort_list_without_comprehension():
    list_without_comprehension().sort(reverse=True)

def sorted_list_without_comprehension():
    sorted(list_without_comprehension())


# operations on list_with_comprehension
def sort_list_with_comprehension():
    list_with_comprehension().sort()

def reverse_sort_list_with_comprehension():
    list_with_comprehension().sort(reverse=True)

def sorted_list_with_comprehension():
    sorted(list_with_comprehension())


def main():
    objs = globals()
    funcs = []
    f = open("timeit_demo.sh", "w+")

    for objname in objs:
        if objname != 'main' and type(objs[objname]) == types.FunctionType:
            funcs.append(objname)
    funcs.sort()
    for func in funcs:
        f.write('''echo "Timing: %(funcname)s"
python -m timeit "import timeit_demo; timeit_demo.%(funcname)s();"\n\n
echo "------------------------------------------------------------"
''' % dict(
                funcname = func,
                )
            )

    f.close()

if __name__ == "__main__":
    main()

    from os import system

    #Works only for *nix platforms
    system("/bin/bash timeit_demo.sh")

    #un-comment below for windows
    #system("cmd timeit_demo.sh")

2

Hier ist eine einfache Hülle für die Antwort von Steven. Diese Funktion führt keine wiederholten Läufe / Mittelungen durch, erspart Ihnen lediglich, den Timing-Code überall wiederholen zu müssen :)

'''function which prints the wall time it takes to execute the given command'''
def time_func(func, *args): #*args can take 0 or more 
  import time
  start_time = time.time()
  func(*args)
  end_time = time.time()
  print("it took this long to run: {}".format(end_time-start_time))

0

Die Testsuite versucht nicht, das Importierte zu verwenden, timeitdaher ist es schwer zu sagen, was die Absicht war. Trotzdem ist dies eine kanonische Antwort, so dass ein vollständiges Beispiel für angebracht timeiterscheint und Martijns Antwort näher erläutert wird .

Die Dokumentetimeit bieten viele Beispiele und Flags, die es wert sind, überprüft zu werden. Die grundlegende Verwendung in der Befehlszeile lautet:

$ python -mtimeit "all(True for _ in range(1000))"
2000 loops, best of 5: 161 usec per loop
$ python -mtimeit "all([True for _ in range(1000)])"
2000 loops, best of 5: 116 usec per loop

Führen Sie mit aus -h, um alle Optionen anzuzeigen. Python MOTW hat einen großartigen Abschnitt übertimeit dem gezeigt wird, wie Module über Import- und mehrzeilige die Befehlszeile ausgeführt werden.

In Skriptform verwende ich es normalerweise so:

import argparse
import copy
import dis
import inspect
import random
import sys
import timeit

def test_slice(L):
    L[:]

def test_copy(L):
    L.copy()

def test_deepcopy(L):
    copy.deepcopy(L)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--n", type=int, default=10 ** 5)
    parser.add_argument("--trials", type=int, default=100)
    parser.add_argument("--dis", action="store_true")
    args = parser.parse_args()
    n = args.n
    trials = args.trials
    namespace = dict(L = random.sample(range(n), k=n))
    funcs_to_test = [x for x in locals().values() 
                     if callable(x) and x.__module__ == __name__]
    print(f"{'-' * 30}\nn = {n}, {trials} trials\n{'-' * 30}\n")

    for func in funcs_to_test:
        fname = func.__name__
        fargs = ", ".join(inspect.signature(func).parameters)
        stmt = f"{fname}({fargs})"
        setup = f"from __main__ import {fname}"
        time = timeit.timeit(stmt, setup, number=trials, globals=namespace)
        print(inspect.getsource(globals().get(fname)))

        if args.dis:
            dis.dis(globals().get(fname))

        print(f"time (s) => {time}\n{'-' * 30}\n")

Sie können ganz einfach die Funktionen und Argumente eingeben, die Sie benötigen. Seien Sie vorsichtig, wenn Sie unreine Funktionen verwenden, und achten Sie auf den Zustand.

Beispielausgabe:

$ python benchmark.py --n 10000
------------------------------
n = 10000, 100 trials
------------------------------

def test_slice(L):
    L[:]

time (s) => 0.015502399999999972
------------------------------

def test_copy(L):
    L.copy()

time (s) => 0.01651419999999998
------------------------------

def test_deepcopy(L):
    copy.deepcopy(L)

time (s) => 2.136012
------------------------------
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.