Bagaimana saya mengatur waktu segmen kode untuk menguji kinerja dengan Pythons timeit?

162

Saya memiliki skrip python yang berfungsi seperti seharusnya, tetapi saya harus menulis waktu eksekusi. Saya sudah googled bahwa saya harus menggunakan timeittetapi sepertinya saya tidak bisa membuatnya bekerja.

Skrip Python saya terlihat seperti ini:

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)

Yang saya butuhkan adalah waktu yang diperlukan untuk menjalankan kueri dan menulisnya ke file results_update.txt. Tujuannya adalah untuk menguji pernyataan pembaruan untuk basis data saya dengan berbagai indeks dan mekanisme penyetelan.

Mestika
sumber
Apakah pertanyaan Anda spesifik timeit? Saya rasa tidak. Dalam hal ini, Anda mungkin harus menghapus "with Pythons timeit" dari judul.
Martin Thoma

Jawaban:

275

Anda dapat menggunakan time.time()atau time.clock()sebelum dan sesudah blok yang Anda inginkan.

import time

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

total = t1-t0

Metode ini tidak setepat timeit(tidak rata-rata beberapa kali berjalan) tetapi langsung.

time.time()(di Windows dan Linux) dan time.clock()(di Linux) tidak cukup tepat untuk fungsi cepat (Anda mendapatkan total = 0). Dalam hal ini atau jika Anda ingin rata-rata waktu berlalu oleh beberapa berjalan, Anda harus secara manual memanggil fungsi beberapa kali (Seperti saya pikir Anda sudah melakukannya dalam kode contoh Anda dan timeit tidak secara otomatis ketika Anda mengatur argumen nomornya )

import time

def myfast():
   code

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

total_n = t1-t0

Di Windows, seperti yang dinyatakan Corey dalam komentar, time.clock()memiliki presisi yang jauh lebih tinggi (mikrodetik, bukan yang kedua) dan lebih disukai time.time().

joaquin
sumber
8
fyi di windows, gunakan time.clock () alih-alih time.time ()
Corey Goldberg
4
Terima kasih Corey, mengapa? karena jam lebih tepat (mikrodetik) atau ada sesuatu yang lebih?
joaquin
11
Anda dapat menggunakan timeit.default_timer () untuk membuat platform kode Anda independen; ia mengembalikan time.clock () atau time.time () yang sesuai untuk OS.
Marc Stober
6
Daripada memilih jam dengan tangan, gunakan timeit.default_timer; Python telah melakukan pekerjaan untuk Anda. Tapi sungguh, Anda harus menggunakan timeit.timeit(myfast, number=n)alih-alih menciptakan kembali roda panggilan berulang (dan kehilangan fakta bahwa timeitmenonaktifkan pengumpul sampah saat menjalankan kode berulang kali).
Martijn Pieters
15
pembaruan: time.clock () sekarang sudah tidak digunakan lagi. Anda sekarang harus menggunakan time.time (). Sebenarnya, sejak versi 3.3, opsi terbaik adalah time.perf_counter ()
Madlozoz
42

Jika Anda membuat profil kode Anda dan dapat menggunakan IPython, ia memiliki fungsi ajaib %timeit.

%%timeit beroperasi pada sel.

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
sumber
36

Terlepas dari timingnya, kode yang Anda perlihatkan ini benar-benar salah: Anda mengeksekusi 100 koneksi (sama sekali mengabaikan semua kecuali yang terakhir), dan kemudian ketika Anda melakukan panggilan eksekusi pertama Anda memberikannya variabel lokal query_stmtyang baru Anda inisialisasi setelah eksekusi panggilan.

Pertama, buat kode Anda benar, tanpa khawatir tentang waktu: yaitu fungsi yang membuat atau menerima koneksi dan melakukan 100 atau 500 atau berapa pun jumlah pembaruan pada koneksi itu, kemudian menutup koneksi. Setelah kode Anda berfungsi dengan benar adalah titik yang tepat untuk memikirkan menggunakannya timeit!

Secara khusus, jika fungsi yang Anda inginkan adalah parameter-kurang yang dipanggil, foobarAnda dapat menggunakan timeit.timeit (2.6 atau lebih baru - ini lebih rumit pada 2.5 dan sebelumnya):

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

Anda sebaiknya menentukan jumlah proses karena default, satu juta, mungkin tinggi untuk kasus penggunaan Anda (menyebabkan menghabiskan banyak waktu dalam kode ini ;-).

Alex Martelli
sumber
26
Setelah berjuang dengan ini untuk beberapa mintues terakhir saya ingin memberi tahu pemirsa di masa depan bahwa Anda juga mungkin ingin melewati variabel pengaturan jika fungsi Anda foobarada di file utama. Seperti ini: timeit.timeit('foobar()','from __main__ import foobar',number=1000)
Rich
3
Dalam Python 2.7.8, Anda cukup menggunakantimeit.timeit( foobar, number=1000 )
9

Fokus pada satu hal spesifik . Disk I / O lambat, jadi saya akan mengambil dari tes jika semua yang akan Anda tweak adalah query database.

Dan jika Anda perlu menghitung waktu pelaksanaan basis data Anda, cari alat basis data sebagai gantinya, seperti menanyakan rencana kueri, dan perhatikan bahwa kinerjanya bervariasi tidak hanya dengan kueri yang tepat dan indeks apa yang Anda miliki, tetapi juga dengan beban data (berapa banyak data Anda telah menyimpan).

Yang mengatakan, Anda cukup meletakkan kode Anda dalam suatu fungsi dan menjalankan fungsi itu dengan timeit.timeit():

def function_to_repeat():
    # ...

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

Ini akan menonaktifkan pengumpulan sampah, berulang kali memanggil function_to_repeat()fungsi, dan menentukan waktu total durasi panggilan yang digunakan timeit.default_timer(), yang merupakan jam paling akurat yang tersedia untuk platform spesifik Anda.

Anda harus pindah kode setup keluar dari fungsi diulang; misalnya, Anda harus terhubung ke database terlebih dahulu, lalu hanya waktu kueri. Gunakan setupargumen untuk mengimpor atau membuat dependensi tersebut, dan meneruskannya ke fungsi Anda:

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

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

akan mengambil global function_to_repeat, var1dan var2dari skrip Anda dan meneruskannya ke fungsi setiap pengulangan.

Martijn Pieters
sumber
Menempatkan kode ke dalam fungsi adalah langkah yang saya cari-karena hanya membuat kode string dan evaling tidak akan terbang untuk apa pun yang tidak sepenuhnya sepele. thx
javadba
2

Saya melihat pertanyaan sudah dijawab, tetapi masih ingin menambahkan 2 sen saya untuk hal yang sama.

Saya juga menghadapi skenario serupa di mana saya harus menguji waktu pelaksanaan untuk beberapa pendekatan dan karenanya menulis skrip kecil, yang memanggil timeit pada semua fungsi yang ditulis di dalamnya.

Script ini juga tersedia sebagai github intisari di sini .

Semoga ini akan membantu Anda dan orang lain.

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
sumber
2

Inilah pembungkus sederhana untuk jawaban steven. Fungsi ini tidak menjalankan berulang / rata-rata, hanya menyelamatkan Anda dari keharusan mengulang kode waktu di mana-mana :)

'''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))
pertukaran informasi
sumber
0

Suite pengujian tidak berupaya menggunakan impor timeitsehingga sulit untuk mengatakan apa maksudnya. Meskipun demikian, ini adalah jawaban kanonik sehingga contoh lengkap dari timeiturutan tampaknya, menguraikan jawaban Martijn .

The docs untuktimeit menawarkan banyak contoh dan bendera senilai check-out. Penggunaan dasar pada baris perintah adalah:

$ 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

Jalankan dengan -huntuk melihat semua opsi. Python MOTW memiliki bagian hebat timeityang menunjukkan cara menjalankan modul melalui impor dan string kode multiline dari baris perintah.

Dalam bentuk skrip, saya biasanya menggunakannya seperti ini:

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

Anda dapat dengan mudah menjatuhkan fungsi dan argumen yang Anda butuhkan. Berhati-hatilah saat menggunakan fungsi yang tidak murni dan jaga keadaan.

Output sampel:

$ 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
sumber
0

Contoh timeit sederhana lainnya:

def your_function_to_test():
   # do some stuff...

time_to_run_100_times = timeit.timeit(lambda: your_function_to_test, number=100)
sam
sumber