2009-05-20 11 views
55

Programuję w python na windows i chciałbym dokładnie zmierzyć czas potrzebny na uruchomienie funkcji. Napisałem funkcję "time_it", która przyjmuje inną funkcję, uruchamia ją i zwraca czas potrzebny na uruchomienie.Dokładny czas funkcji w python

def time_it(f, *args): 
    start = time.clock() 
    f(*args) 
    return (time.clock() - start)*1000 

nazywam to 1000 razy i średnie wyniki. (Stała 1000 na końcu to udzielenie odpowiedzi w milisekundach.)

Ta funkcja wydaje się działać, ale mam dokuczliwe uczucie, że robię coś złego, i robiąc to w ten sposób, używam więcej czasu niż funkcja faktycznie używa, gdy działa.

Czy istnieje bardziej standardowy lub akceptowany sposób na zrobienie tego?

Kiedy zmieniłem funkcję testu, aby wywołać wydruk, aby trwało dłużej, funkcja time_it zwraca średnio 2,5 ms, podczas gdy cProfile.run ("f()" zwraca średnią wartość 7.0 ms. Pomyślałem, że moja funkcja przeceniłaby czas, jeśli cokolwiek, co tu się dzieje?

Jedna uwaga dodatkowa, to względny czas funkcji względem siebie, na którym mi zależy, a nie czas bezwzględny, ponieważ oczywiście będzie się on różnić w zależności od sprzętu i innych czynników.

Odpowiedz

33

Zamiast pisać własny kod profilowania, proponujemy zapoznać się z wbudowanych profilarek Python (profile lub cProfile, w zależności od potrzeb): http://docs.python.org/library/profile.html

+0

Zignoruj ​​mnie - ten ciąg nie jest nazwą funkcji, jest blokiem kodu eval'd. Możesz więc użyć go do szybkiego pomiaru czasu. To jest właściwa odpowiedź. A w innych wiadomościach - "nie jest" jest znacznie szybszy niż "! =" - ale może mieć inne implikacje. –

+1

I od tej stycznej - zanim użyjesz "nie jest" dla niczego sprytnego - miej to na uwadze - http://stackoverflow.com/questions/1392433/python-why-is-hello-is-hello –

65

Użyj przycisku ze standardowej biblioteki Pythona.

Podstawowe użycie:

from timeit import Timer 

# first argument is the code to be run, the second "setup" argument is only run once, 
# and it not included in the execution time. 
t = Timer("""x.index(123)""", setup="""x = range(1000)""") 

print t.timeit() # prints float, for example 5.8254 
# ..or.. 
print t.timeit(1000) # repeat 1000 times instead of the default 1million 
+2

Chcę mojej funkcji do wywołania z różnymi argumentami, ale kiedy wywołuję t = timeit.Timer ("f()", "od ___main___ import f") z różnymi argumentami i uruchamiam t.timeit (10000) ponownie, otrzymuję te same wyniki, chociaż różne argumenty powinny powodować bardzo różne czasy działania. –

20

Kod ten jest bardzo niedokładna

total= 0 
for i in range(1000): 
    start= time.clock() 
    function() 
    end= time.clock() 
    total += end-start 
time= total/1000 

Kod ten jest mniej niedokładne

start= time.clock() 
for i in range(1000): 
    function() 
end= time.clock() 
time= (end-start)/1000 

Bardzo niedokładne cierpi z powodu błędu pomiaru, jeśli w czasie wykonywania Funkcja jest zbliżona do dokładności zegara. Większość mierzonych czasów to jedynie liczby losowe od 0 do kilku tyknięć zegara.

W zależności od obciążenia systemu, "czas" obserwowany z pojedynczej funkcji może być w całości artefaktem planowania systemu operacyjnego i innych niekontrolowanych kosztów ogólnych.

Druga wersja (mniej niedokładna) ma mniejszą wartość polaryzacji pomiaru. Jeśli twoja funkcja jest naprawdę szybka, być może będziesz musiał uruchomić ją 10 000 razy, aby zminimalizować planowanie systemu operacyjnego i inne narzuty.

Obie są, oczywiście, bardzo mylące. Czas działania twojego programu - jako całości - nie jest sumą czasu działania funkcji. Liczb można używać tylko do porównań względnych. Nie są to bezwzględne pomiary, które mają duże znaczenie.

+1

Dlaczego/1000? Metoda time.clock() zwraca sekundy jako wartość zmiennoprzecinkową. Jeśli spodziewałeś się, że powróci milisekundy, miałoby to sens, jednak podzieląc 1000 konwersji na kilosekundy, jednostkę, której nigdy wcześniej nie widziałem. – pixelgrease

+0

@pixelgrease milli/1000 = micro, not kilo :) – stenci

+0

@stenci Sugeruje, że wynikowa wartość jest w sekundach, np. 1000 sekund. Jeśli podzielisz go na 1000, otrzymasz 1 "kilosekundy". – zehelvion

13

Jeśli chcesz czasować metodę pythona, nawet jeśli blok, który mierzysz, może rzucać, dobrym sposobem jest użycie instrukcji with. Zdefiniować pewne Timer klasy jako

import time 

class Timer:  
    def __enter__(self): 
     self.start = time.clock() 
     return self 

    def __exit__(self, *args): 
     self.end = time.clock() 
     self.interval = self.end - self.start 

Wtedy może chcesz czasie metodę połączenia, które mogą rzucić.Użyj metody, która zostanie wywołana, nawet jeśli żądanie połączenia zostanie odebrane. Dokładniej, trzeba użyć tryfinally aby zobaczyć wynik w przypadku rzutów, jak z

try: 
    with Timer() as t: 
     conn = httplib.HTTPConnection('google.com') 
     conn.request('GET', '/') 
finally: 
    print('Request took %.03f sec.' % t.interval) 

More details here.

23

Można stworzyć „timeme” dekorator podobnie jak

import time             

def timeme(method): 
    def wrapper(*args, **kw): 
     startTime = int(round(time.time() * 1000)) 
     result = method(*args, **kw) 
     endTime = int(round(time.time() * 1000)) 

     print(endTime - startTime,'ms') 
     return result 

    return wrapper 

@timeme 
def func1(a,b,c = 'c',sleep = 1): 
    time.sleep(sleep) 
    print(a,b,c) 

func1('a','b','c',0) 
func1('a','b','c',0.5) 
func1('a','b','c',0.6) 
func1('a','b','c',1) 
+1

+ n dla tej odpowiedzi. Chciałbym mieć taką opcję. Piękno to to, że mogę wyeksportować wyniki logów do zewnętrznego pliku i dodać to gdziekolwiek potrzebuję! Dziękuję za milion. – curlyreggie

+5

Działa to doskonale w przypadku funkcji nierekurencyjnych. W przypadku funkcji rekursywnych zwraca czas dla każdej iteracji funkcji. – tachijuan

6

To jest łatwiejsze niż

from contextlib import contextmanager 

import time 
@contextmanager 
def timeblock(label): 
    start = time.clock() 
    try: 
     yield 
    finally: 
     end = time.clock() 
     print ('{} : {}'.format(label, end - start)) 



with timeblock("just a test"): 
      print "yippee" 
+0

Dobra odpowiedź, prosta i jak dotąd jedyna, którą znalazłem, która pozwala wysłać etykietę do funkcji timera. – Gabriel

+0

Czyste rozwiązanie, ale nieco zbyt skomplikowane. Spędziłem godzinę próbując dowiedzieć się, dlaczego 'time.sleep (10)' wykonano tylko 0,002 sekundy, aby wykonać zgodnie z tym kodem. (btw, duża różnica między 'time.clock()' a 'time.time()' w pythonie) – swdev

4

Podobny do odpowiedzi @ AlexMartelli

import timeit 
timeit.timeit(fun, number=10000) 

może załatwić sprawę.