Jak określić czas segmentu kodu do testowania wydajności w Pythonie Timeit?

162

Mam skrypt w Pythonie, który działa tak, jak powinien, ale muszę napisać czas wykonania. Wyszukałem w Google, że powinienem użyć, timeitale nie mogę go uruchomić.

Mój skrypt w Pythonie wygląda następująco:

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)

Potrzebuję czasu potrzebnego na wykonanie zapytania i zapisanie go w pliku results_update.txt. Celem jest przetestowanie instrukcji aktualizacji dla mojej bazy danych z różnymi indeksami i mechanizmami dostrajania.

Mestika
źródło
Czy Twoje pytanie było / jest konkretne timeit? Nie sądzę. W takim przypadku prawdopodobnie powinieneś usunąć z tytułu "with Pythons timeit".
Martin Thoma

Odpowiedzi:

275

Możesz użyć time.time()lub time.clock()przed i po bloku, który chcesz czas.

import time

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

total = t1-t0

Ta metoda nie jest tak dokładna jak timeit(nie uśrednia kilku przebiegów), ale jest prosta.

time.time()(w Windows i Linux) i time.clock()(w Linuksie) nie są wystarczająco precyzyjne dla szybkich funkcji (otrzymujesz łącznie = 0). W takim przypadku lub jeśli chcesz uśrednić czas, który upłynął przez kilka uruchomień, musisz ręcznie wywołać funkcję wiele razy (jak myślę, że już robisz w swoim przykładowym kodzie i czasie robi się to automatycznie, gdy ustawisz jego argument liczbowy )

import time

def myfast():
   code

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

total_n = t1-t0

W systemie Windows, jak stwierdził Corey w komentarzu, time.clock()ma znacznie wyższą precyzję (mikrosekunda zamiast sekundy) i jest preferowana time.time().

joaquin
źródło
8
fyi w systemie Windows, użyj time.clock () zamiast time.time ()
Corey Goldberg
4
Dzięki Corey, dlaczego? bo zegar jest dokładniejszy (mikrosekundy) czy jest coś więcej?
joaquin
11
Możesz użyć timeit.default_timer (), aby uniezależnić swoją platformę kodu; zwraca odpowiednio time.clock () lub time.time (), stosownie do systemu operacyjnego.
Marc Stober
6
Zamiast wybierać zegar ręcznie, użyj timeit.default_timer; Python już wykonał za Ciebie pracę. Ale tak naprawdę powinieneś używać timeit.timeit(myfast, number=n)zamiast wymyślać na nowo koło powtarzalnych wywołań (i przegapić fakt, że timeitwyłącza moduł odśmiecania pamięci podczas wielokrotnego uruchamiania kodu).
Martijn Pieters
15
update: time.clock () jest teraz przestarzała. Powinieneś teraz użyć time.time (). Właściwie od wersji 3.3 najlepszą opcją byłby time.perf_counter ()
Madlozoz
42

Jeśli profilujesz swój kod i możesz używać IPython, ma on magiczną funkcję %timeit.

%%timeit działa na komórkach.

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
munk
źródło
36

Zupełnie niezależnie od czasu, ten kod, który pokazujesz jest po prostu niepoprawny: wykonujesz 100 połączeń (całkowicie ignorując wszystkie oprócz ostatniego), a następnie, gdy wykonujesz pierwsze wywołanie wykonania, przekazujesz mu zmienną lokalną, query_stmtktórą inicjujesz dopiero po wykonaniu połączenie.

Po pierwsze, popraw kod, nie martwiąc się jeszcze o czas: tj. Funkcja, która nawiązuje lub odbiera połączenie i wykonuje 100 lub 500 lub dowolną liczbę aktualizacji na tym połączeniu, a następnie zamyka połączenie. Gdy Twój kod działa poprawnie, to właściwy punkt, w którym należy pomyśleć o jego użyciu timeit!

W szczególności, jeśli funkcja, którą chcesz mierzyć czas, jest bez parametrów, o nazwie foobar, możesz użyć timeit.timeit (2.6 lub nowszy - jest bardziej skomplikowany w 2.5 i wcześniejszych):

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

Lepiej określ liczbę uruchomień, ponieważ wartość domyślna, milion, może być wysoka dla twojego przypadku użycia (prowadząc do spędzenia dużo czasu w tym kodzie ;-).

Alex Martelli
źródło
26
Po zmaganiach z tym przez kilka ostatnich minut, chcę poinformować przyszłych widzów, że prawdopodobnie chcesz również przekazać zmienną konfiguracyjną, jeśli twoja funkcja foobarznajduje się w pliku głównym. W ten sposób: timeit.timeit('foobar()','from __main__ import foobar',number=1000)
Rich
3
W Pythonie 2.7.8 możesz po prostu użyćtimeit.timeit( foobar, number=1000 )
9

Skoncentruj się na jednej konkretnej rzeczy . We / wy dysku jest powolne, więc usunąłbym to z testu, jeśli wszystko, co zamierzasz ulepszyć, to zapytanie do bazy danych.

A jeśli potrzebujesz czasu na wykonanie bazy danych, poszukaj zamiast tego narzędzi bazodanowych, takich jak pytanie o plan zapytań, i zwróć uwagę, że wydajność zależy nie tylko od dokładnego zapytania i posiadanych indeksów, ale także od obciążenia danymi (ile danych zapisałeś).

To powiedziawszy, możesz po prostu umieścić swój kod w funkcji i uruchomić tę funkcję za pomocą timeit.timeit():

def function_to_repeat():
    # ...

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

Spowoduje to wyłączenie czyszczenia pamięci, wielokrotne wywoływanie function_to_repeat()funkcji i timeit.default_timer()określanie czasu całkowitego czasu trwania tych wywołań przy użyciu najdokładniejszego dostępnego zegara dla określonej platformy.

Należy przenieść kod instalacyjny się z powtarzających się czynności; na przykład należy najpierw połączyć się z bazą danych, a dopiero potem mierzyć czas tylko na zapytania. Użyj setupargumentu, aby zaimportować lub utworzyć te zależności i przekaż je do swojej funkcji:

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

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

by chwycić globalnych function_to_repeat, var1a var2od skryptu i przekazać te do funkcji każdego powtórzenia.

Martijn Pieters
źródło
Umieszczenie kodu w funkcji jest krokiem, którego szukałem, ponieważ po prostu uczynienie kodu łańcuchem znaków, a evaling nie będzie latać dla niczego, co nie jest całkowicie trywialne. thx
javadba
2

Widzę, że odpowiedź na to pytanie została już udzielona, ​​ale nadal chcę dodać moje 2 centy za to samo.

Miałem również do czynienia z podobnym scenariuszem, w którym muszę przetestować czasy wykonywania dla kilku podejść i dlatego napisałem mały skrypt, który wywołuje timeit na wszystkich napisanych w nim funkcjach.

Skrypt jest również dostępny w formie github tutaj .

Mam nadzieję, że pomoże to Tobie i innym.

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")
Abhijit Mamarde
źródło
2

Oto proste opakowanie odpowiedzi Stevena. Ta funkcja nie wykonuje powtarzających się przebiegów / uśredniania, po prostu oszczędza Ci konieczności powtarzania kodu czasowego wszędzie :)

'''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))
wymiana_informacyjna
źródło
0

Pakiet testowy nie próbuje użyć importowanego, timeitwięc trudno powiedzieć, jaki był zamiar. Niemniej jednak jest to odpowiedź kanoniczna, więc kompletny przykład timeitwydaje się w porządku, rozwijając odpowiedź Martijna .

W docs dlatimeit oferują wiele przykładów i flagi warto sprawdzić. Podstawowe użycie w wierszu poleceń to:

$ 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

Biegnij z, -haby zobaczyć wszystkie opcje. Python MOTW ma świetną sekcję, timeitktóra pokazuje, jak uruchamiać moduły za pomocą importu i wielowierszowych ciągów kodu z wiersza poleceń.

W formie skryptu zazwyczaj używam go w następujący sposób:

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

Możesz dość łatwo dodać potrzebne funkcje i argumenty. Należy zachować ostrożność podczas używania nieczystych funkcji i dbać o stan.

Przykładowe dane wyjściowe:

$ 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
------------------------------
ggorlen
źródło