Cara menggunakan modul timeit

351

Saya mengerti konsep apa yang timeitdilakukan tetapi saya tidak yakin bagaimana mengimplementasikannya dalam kode saya.

Bagaimana saya bisa membandingkan dua fungsi, katakan insertion_sortdan tim_sort, dengan timeit?

Neemaximo
sumber

Jawaban:

266

Cara timeit bekerja adalah dengan menjalankan kode pengaturan sekali dan kemudian membuat panggilan berulang ke serangkaian pernyataan. Jadi, jika Anda ingin menguji penyortiran, diperlukan kehati-hatian agar satu pass pada in-place sort tidak mempengaruhi pass berikutnya dengan data yang sudah diurutkan (yang, tentu saja, akan membuat Timsort benar-benar bersinar karena berkinerja terbaik. ketika data sudah dipesan sebagian).

Berikut adalah contoh cara menyiapkan tes untuk penyortiran:

>>> import timeit

>>> setup = '''
import random

random.seed('slartibartfast')
s = [random.random() for i in range(1000)]
timsort = list.sort
'''

>>> print min(timeit.Timer('a=s[:]; timsort(a)', setup=setup).repeat(7, 1000))
0.334147930145

Perhatikan bahwa rangkaian pernyataan membuat salinan baru dari data yang tidak disortir pada setiap pass.

Juga, perhatikan teknik pengaturan waktu menjalankan rangkaian pengukuran tujuh kali dan hanya menjaga waktu terbaik - ini benar-benar dapat membantu mengurangi distorsi pengukuran karena proses lain yang berjalan pada sistem Anda.

Itulah tips saya untuk menggunakan timeit dengan benar. Semoga ini membantu :-)

Raymond Hettinger
sumber
8
Ya, itu termasuk salinan daftar (yang sangat cepat dibandingkan dengan jenis itu sendiri). Jika Anda tidak menyalin, pass pertama mengurutkan daftar dan pass yang tersisa tidak perlu melakukan pekerjaan apa pun. Jika Anda ingin mengetahui waktu hanya untuk pengurutan, jalankan di atas dengan dan tanpa timsort(a)dan ambil perbedaan :-)
Raymond Hettinger
Saya akan merekomendasikan untuk mengulangi 7 kali untuk setiap pengaturan, dan kemudian rata-rata; bukan sebaliknya. Dengan cara ini, jika setiap lonjakan karena proses lain memiliki peluang bagus untuk diabaikan sepenuhnya, daripada dirata-rata.
Maks
75
@max Gunakan min () daripada rata-rata timing. Itu adalah rekomendasi dari saya, dari Tim Peters, dan dari Guido van Rossum. Waktu tercepat merupakan yang terbaik yang dapat dilakukan oleh algoritma ketika cache dimuat dan sistem tidak sibuk dengan tugas-tugas lain. Semua timing berisik - waktu tercepat adalah yang paling tidak berisik. Sangat mudah untuk menunjukkan bahwa pengaturan waktu tercepat adalah yang paling dapat direproduksi dan oleh karena itu paling berguna ketika menentukan waktu dua implementasi yang berbeda.
Raymond Hettinger
4
Anda menghitung rata - rata (baik, total, tetapi setara) untuk 1000 input; lalu ulangi 7 kali, dan ambil minimum . Anda memerlukan rata-rata lebih dari 1000 input karena Anda ingin kompleksitas algoritma rata-rata (bukan yang terbaik). Anda membutuhkan minimum karena alasan yang Anda berikan. Saya pikir saya dapat meningkatkan pendekatan Anda dengan memilih satu input, menjalankan algoritma 7 kali, mengambil minimum; kemudian ulangi untuk 1000 input berbeda, dan ambil rata-rata. Yang tidak saya sadari adalah Anda .repeat(7,1000)sudah melakukan ini (dengan menggunakan benih yang sama)! Jadi solusi Anda adalah IMO yang sempurna.
maks
5
Saya hanya dapat menambahkan bahwa bagaimana Anda mengalokasikan anggaran Anda dari 7000 eksekusi (misalnya, .repeat(7, 1000)vs .repeat(2, 3500)vs .repeat(35, 200) harus bergantung pada bagaimana kesalahan karena beban sistem dibandingkan dengan kesalahan karena variabilitas input. Dalam kasus ekstrem jika sistem Anda selalu di bawah beban berat, dan Anda melihat ekor tipis panjang di sebelah kiri distribusi waktu eksekusi (ketika Anda menangkapnya dalam keadaan idle langka), Anda bahkan mungkin menemukan .repeat(7000,1)lebih berguna daripada .repeat(7,1000)jika Anda tidak dapat menganggarkan lebih dari 7000 berjalan.
maks
277

Jika Anda ingin menggunakan timeitsesi Python interaktif, ada dua opsi yang mudah:

  1. Gunakan shell IPython . Ini fitur %timeitfungsi khusus yang nyaman :

    In [1]: def f(x):
       ...:     return x*x
       ...: 
    
    In [2]: %timeit for x in range(100): f(x)
    100000 loops, best of 3: 20.3 us per loop
  2. Dalam juru bahasa Python standar, Anda dapat mengakses fungsi dan nama lain yang Anda tentukan sebelumnya selama sesi interaktif dengan mengimpornya dari __main__dalam pernyataan pengaturan:

    >>> def f(x):
    ...     return x * x 
    ... 
    >>> import timeit
    >>> timeit.repeat("for x in range(100): f(x)", "from __main__ import f",
                      number=100000)
    [2.0640320777893066, 2.0876040458679199, 2.0520210266113281]
Sven Marnach
sumber
97
+1 untuk menunjukkan from __main__ import ftekniknya. Saya tidak berpikir ini dikenal secara luas sebagaimana mestinya. Ini berguna dalam kasus-kasus seperti ini di mana panggilan fungsi atau metode sedang diatur waktunya. Dalam kasus lain (mengatur serangkaian langkah), ini kurang membantu karena memperkenalkan overhead panggilan fungsi.
Raymond Hettinger
15
Anda bisa melakukannya%timeit f(x)
qed
Catatan: pengaturan "impor f" membuat akses ke pembacaan lokal cepat fa - yang sebenarnya tidak mencerminkan panggilan fungsi global (fungsi cepat singkat) dalam kode normal yang umum. Dalam Py3.5 + global nyata dapat diberikan: "Diubah dalam versi 3.5: Parameter global opsional telah ditambahkan."; Sebelum global dari modul timeit di mana tak terhindarkan (yang tidak masuk akal). Mungkin global dari kode panggilan ( sys._getframe(N).f_globals) seharusnya menjadi default dari awal.
kxr
140

Saya akan memberi Anda rahasia: cara terbaik untuk menggunakan timeitadalah di baris perintah.

Di baris perintah, timeitlakukan analisis statistik yang tepat: ini memberi tahu Anda berapa lama waktu yang paling singkat. Ini bagus karena semua kesalahan dalam pengaturan waktu adalah positif. Jadi waktu tersingkat memiliki kesalahan paling sedikit di dalamnya. Tidak ada cara untuk mendapatkan kesalahan negatif karena komputer tidak pernah dapat menghitung lebih cepat daripada yang bisa dihitung!

Jadi, antarmuka baris perintah:

%~> python -m timeit "1 + 2"
10000000 loops, best of 3: 0.0468 usec per loop

Itu cukup sederhana, eh?

Anda dapat mengatur barang-barang:

%~> python -m timeit -s "x = range(10000)" "sum(x)"
1000 loops, best of 3: 543 usec per loop

yang bermanfaat juga!

Jika Anda ingin banyak baris, Anda bisa menggunakan kelanjutan otomatis shell atau menggunakan argumen terpisah:

%~> python -m timeit -s "x = range(10000)" -s "y = range(100)" "sum(x)" "min(y)"
1000 loops, best of 3: 554 usec per loop

Itu memberikan pengaturan

x = range(1000)
y = range(100)

dan waktu

sum(x)
min(y)

Jika Anda ingin memiliki skrip yang lebih panjang, Anda mungkin tergoda untuk pindah ke timeitdalam skrip Python. Saya sarankan menghindari itu karena analisis dan pengaturan waktu hanya lebih baik di baris perintah. Sebaliknya, saya cenderung membuat skrip shell:

 SETUP="

 ... # lots of stuff

 "

 echo Minmod arr1
 python -m timeit -s "$SETUP" "Minmod(arr1)"

 echo pure_minmod arr1
 python -m timeit -s "$SETUP" "pure_minmod(arr1)"

 echo better_minmod arr1
 python -m timeit -s "$SETUP" "better_minmod(arr1)"

 ... etc

Ini bisa memakan waktu sedikit lebih lama karena beberapa inisialisasi, tetapi biasanya itu bukan masalah besar.


Tetapi bagaimana jika Anda ingin menggunakan timeitdi dalam modul Anda?

Nah, cara sederhana adalah melakukan:

def function(...):
    ...

timeit.Timer(function).timeit(number=NUMBER)

dan itu memberi Anda waktu kumulatif ( bukan minimum!) untuk menjalankan jumlah itu.

Untuk mendapatkan analisis yang baik, gunakan .repeatdan ambil minimum:

min(timeit.Timer(function).repeat(repeat=REPEATS, number=NUMBER))

Anda biasanya harus menggabungkan ini dengan functools.partialbukannya lambda: ...menurunkan overhead. Dengan demikian Anda dapat memiliki sesuatu seperti:

from functools import partial

def to_time(items):
    ...

test_items = [1, 2, 3] * 100
times = timeit.Timer(partial(to_time, test_items)).repeat(3, 1000)

# Divide by the number of repeats
time_taken = min(times) / 1000

Anda juga dapat melakukan:

timeit.timeit("...", setup="from __main__ import ...", number=NUMBER)

yang akan memberi Anda sesuatu yang lebih dekat ke antarmuka dari command-line, tetapi dengan cara yang jauh lebih keren. The "from __main__ import ..."memungkinkan Anda menggunakan kode dari modul utama Anda dalam lingkungan buatan yang dibuat oleh timeit.

Perlu dicatat bahwa ini adalah pembungkus yang nyaman Timer(...).timeit(...)dan karenanya tidak terlalu bagus dalam hal waktu. Saya pribadi jauh lebih suka menggunakan Timer(...).repeat(...)seperti yang saya tunjukkan di atas.


Peringatan

Ada beberapa peringatan dengan timeityang menahan di mana-mana.

  • Overhead tidak diperhitungkan. Katakan Anda ingin waktu x += 1, untuk mengetahui berapa lama penambahan:

    >>> python -m timeit -s "x = 0" "x += 1"
    10000000 loops, best of 3: 0.0476 usec per loop

    Yah, ini bukan 0,0476 μs. Anda hanya tahu bahwa itu kurang dari itu. Semua kesalahan adalah positif.

    Jadi cobalah dan temukan overhead murni :

    >>> python -m timeit -s "x = 0" ""      
    100000000 loops, best of 3: 0.014 usec per loop

    Itu overhead 30% baik hanya dari waktu! Ini secara besar-besaran dapat mengubah pengaturan waktu relatif. Tetapi Anda hanya benar-benar peduli tentang penambahan waktu; timing pencarian untuk xjuga perlu dimasukkan dalam overhead:

    >>> python -m timeit -s "x = 0" "x"
    100000000 loops, best of 3: 0.0166 usec per loop

    Perbedaannya tidak jauh lebih besar, tetapi ada di sana.

  • Metode bermutasi berbahaya.

    >>> python -m timeit -s "x = [0]*100000" "while x: x.pop()"
    10000000 loops, best of 3: 0.0436 usec per loop

    Tapi itu sama sekali salah! xadalah daftar kosong setelah iterasi pertama. Anda harus menginisialisasi ulang:

    >>> python -m timeit "x = [0]*100000" "while x: x.pop()"
    100 loops, best of 3: 9.79 msec per loop

    Tetapi kemudian Anda memiliki banyak overhead. Akun untuk itu secara terpisah.

    >>> python -m timeit "x = [0]*100000"                   
    1000 loops, best of 3: 261 usec per loop

    Perhatikan bahwa mengurangi overhead adalah wajar di sini hanya karena overhead adalah sebagian kecil dari waktu.

    Sebagai contoh Anda, perlu dicatat bahwa kedua Sisipan Penyisipan dan Tim Mengurutkan memiliki perilaku waktu yang sama sekali tidak biasa untuk daftar yang sudah diurutkan. Ini berarti Anda akan memerlukan random.shuffleantara macam-macam jika Anda ingin menghindari merusak waktu Anda.

Veedrac
sumber
1
apa yang kami maksud? apakah ini mikrodetik?
Hasan Iqbal
2
@HasanIqbalAnik Ya.
Veedrac
@StefanPochmann Karena tidak mencoba sampel beberapa kali.
Veedrac
@Veedrac Mempertimbangkan pernyataan Anda tentang pengurangan overhead pengaturan waktu murni, timeitjalankan passpernyataan ketika tidak ada argumen yang diberikan, yang tentu saja membutuhkan waktu. Jika ada argumen yang diberikan, tidakpass akan dieksekusi, jadi kurangi beberapa usecs dari setiap waktu akan salah. 0.014
Arne
99

Jika Anda ingin membandingkan dua blok kode / fungsi dengan cepat yang dapat Anda lakukan:

import timeit

start_time = timeit.default_timer()
func1()
print(timeit.default_timer() - start_time)

start_time = timeit.default_timer()
func2()
print(timeit.default_timer() - start_time)
zzart
sumber
43

Saya menemukan cara termudah untuk menggunakan timeit adalah dari baris perintah:

Diberikan test.py :

def InsertionSort(): ...
def TimSort(): ...

jalankan timeit seperti ini:

% python -mtimeit -s'import test' 'test.InsertionSort()'
% python -mtimeit -s'import test' 'test.TimSort()'
unutbu
sumber
18

bagi saya, ini adalah cara tercepat:

import timeit
def foo():
    print("here is my code to time...")


timeit.timeit(stmt=foo, number=1234567)
Rodrigo Laguna
sumber
12
# Генерация целых чисел

def gen_prime(x):
    multiples = []
    results = []
    for i in range(2, x+1):
        if i not in multiples:
            results.append(i)
            for j in range(i*i, x+1, i):
                multiples.append(j)

    return results


import timeit

# Засекаем время

start_time = timeit.default_timer()
gen_prime(3000)
print(timeit.default_timer() - start_time)

# start_time = timeit.default_timer()
# gen_prime(1001)
# print(timeit.default_timer() - start_time)
David Webb
sumber
7

Ini sangat bagus:

  python -m timeit -c "$(cat file_name.py)"
Ohad Rubin
sumber
Apa yang setara dengan Windows?
Shailen
2
Bagaimana Anda menyampaikan parameter, jika skrip memerlukannya?
Juuso Ohtonen
3

memungkinkan pengaturan kamus yang sama di masing-masing berikut dan menguji waktu eksekusi.

Argumen pengaturan pada dasarnya adalah mengatur kamus

Nomor untuk menjalankan kode 1000000 kali. Bukan pengaturan tetapi stmt

Ketika Anda menjalankan ini, Anda dapat melihat bahwa indeks jauh lebih cepat daripada mendapatkan. Anda dapat menjalankannya beberapa kali untuk melihatnya.

Kode ini pada dasarnya mencoba untuk mendapatkan nilai c dalam kamus.

import timeit

print('Getting value of C by index:', timeit.timeit(stmt="mydict['c']", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))
print('Getting value of C by get:', timeit.timeit(stmt="mydict.get('c')", setup="mydict={'a':5, 'b':6, 'c':7}", number=1000000))

Inilah hasil saya, hasil Anda akan berbeda.

berdasarkan indeks: 0.20900007452246427

oleh get: 0.54841166886888

Stryker
sumber
Versi python apa yang Anda gunakan?
Eduardo
3

cukup kirimkan seluruh kode Anda sebagai argumen timeit:

import timeit

print(timeit.timeit(

"""   
limit = 10000
prime_list = [i for i in range(2, limit+1)]

for prime in prime_list:
    for elem in range(prime*2, max(prime_list)+1, prime):
        if elem in prime_list:
            prime_list.remove(elem)
"""   
, number=10))
Sébastien Wieckowski
sumber
1
import timeit


def oct(x):
   return x*x


timeit.Timer("for x in range(100): oct(x)", "gc.enable()").timeit()
Yagmur SAHIN
sumber
Apa gc.enable()?
Robin Andrews
0

Modul timeit bawaan berfungsi paling baik dari baris perintah IPython.

Fungsi waktu dari dalam modul:

from timeit import default_timer as timer
import sys

def timefunc(func, *args, **kwargs):
    """Time a function. 

    args:
        iterations=3

    Usage example:
        timeit(myfunc, 1, b=2)
    """
    try:
        iterations = kwargs.pop('iterations')
    except KeyError:
        iterations = 3
    elapsed = sys.maxsize
    for _ in range(iterations):
        start = timer()
        result = func(*args, **kwargs)
        elapsed = min(timer() - start, elapsed)
    print(('Best of {} {}(): {:.9f}'.format(iterations, func.__name__, elapsed)))
    return result
ChaimG
sumber
0

Contoh cara menggunakan interpreter Python REPL dengan fungsi yang menerima parameter.

>>> import timeit                                                                                         

>>> def naive_func(x):                                                                                    
...     a = 0                                                                                             
...     for i in range(a):                                                                                
...         a += i                                                                                        
...     return a                                                                                          

>>> def wrapper(func, *args, **kwargs):                                                                   
...     def wrapper():                                                                                    
...         return func(*args, **kwargs)                                                                  
...     return wrapper                                                                                    

>>> wrapped = wrapper(naive_func, 1_000)                                                                  

>>> timeit.timeit(wrapped, number=1_000_000)                                                              
0.4458435332577161                                                                                        
Vlad Bezden
sumber
0

Anda akan membuat dua fungsi dan menjalankan sesuatu yang mirip dengan ini. Perhatikan, Anda ingin memilih jumlah eksekusi / lari yang sama untuk membandingkan apel dengan apel.
Ini diuji dengan Python 3.7.

masukkan deskripsi gambar di sini Berikut adalah kode untuk kemudahan menyalinnya

!/usr/local/bin/python3
import timeit

def fibonacci(n):
    """
    Returns the n-th Fibonacci number.
    """
    if(n == 0):
        result = 0
    elif(n == 1):
        result = 1
    else:
        result = fibonacci(n-1) + fibonacci(n-2)
    return result

if __name__ == '__main__':
    import timeit
    t1 = timeit.Timer("fibonacci(13)", "from __main__ import fibonacci")
    print("fibonacci ran:",t1.timeit(number=1000), "milliseconds")
grepit
sumber