Як я можу розробити сегмент коду для тестування продуктивності за допомогою Pythons timeit?


162

У мене є сценарій python, який працює так, як слід, але мені потрібно записати час виконання. Я погуглив, що я повинен використовувати, timeitале, здається, не можу змусити його працювати.

Мій сценарій Python виглядає так:

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)

Мені потрібно час, який потрібен для виконання запиту та запису його у файл results_update.txt. Мета - перевірити оператор оновлення для моєї бази даних з різними індексами та механізмами настройки.


Чи було / вашим питанням конкретно timeit? Я думаю, що не. У такому випадку ви, ймовірно, повинні видалити "з Pythons timeit" із заголовка.
Мартін Тома

Відповіді:


275

Ви можете використовувати time.time()або time.clock()до, і після блоку, який ви хочете ввести.

import time

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

total = t1-t0

Цей метод не настільки точний, як timeit(він не складає в середньому кілька запусків), але він простий.

time.time()(в Windows і Linux) і time.clock()(в Linux) недостатньо точні для швидких функцій (ви отримуєте сумарно = 0). У цьому випадку або якщо ви хочете середній час, який минув кілька запусків, вам доведеться вручну викликати функцію кілька разів (Як я думаю, ви вже робите у вас приклад коду, і timeit робить автоматично, коли ви встановлюєте аргумент його числа )

import time

def myfast():
   code

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

total_n = t1-t0

У Windows, як Corey заявив у коментарі, time.clock()має набагато більшу точність (мікросекунда замість секунди) і віддається перевагу над time.time().


8
fyi на windows, використовуйте time.clock () замість time.time ()
Corey Goldberg

4
Дякую Корі, чому? адже годинник точніший (мікросекунди) чи є щось більше?
Хоакін

11
Ви можете використовувати timeit.default_timer (), щоб зробити свою кодову платформу незалежною; він повертає або time.clock (), або time.time (), як підходить для ОС.
Марк Стобер

6
Замість того, щоб вибрати годинник вручну, користуйтеся timeit.default_timer; Python вже зробив роботу за вас. Але дійсно, вам слід використовувати timeit.timeit(myfast, number=n)замість того, щоб вигадувати колесо, що повторюється (і пропустити той факт, що timeitвимикає сміттєзбірник під час повторного запуску коду).
Martijn Pieters

15
оновлення: time.clock () тепер застаріло. Тепер слід використовувати time.time (). Власне, з версії 3.3 найкращим варіантом буде time.perf_counter ()
Madlozoz

42

Якщо ви профілюєте свій код і можете використовувати IPython, він має магічну функцію %timeit.

%%timeit діє на клітини.

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

За винятком часу, цей код, який ви показуєте, є просто неправильним: ви виконуєте 100 з'єднань (повністю ігноруючи всі, крім останнього), а потім, коли ви виконуєте перший виклик виконання, передаєте йому локальну змінну, query_stmtяку ви ініціалізуєте лише після виконання. дзвінок.

Спочатку зробіть свій код правильним, не турбуючись про час часу: тобто функція, яка встановлює або приймає з'єднання і виконує 100 або 500 або будь-яку кількість оновлень для цього з'єднання, потім закриває з'єднання. Після правильного роботи коду - це правильний момент, коли можна задуматися про його використання timeit!

Зокрема, якщо функція, яку ви хочете ввімкнути, є менш параметром, що називається, foobarви можете використовувати timeit.timeit (2.6 або пізнішої версії - це складніше в 2.5 і раніше):

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

Ви краще вкажіть кількість запусків, оскільки за замовчуванням мільйон може бути високим для вашої справи використання (це призведе до того, що ви витратите багато часу на цей код ;-).


26
Після боротьби з цим протягом останніх декількох хвилин я хочу повідомити майбутнім глядачам, що ви, ймовірно, хочете передати змінну налаштування, якщо ваша функція foobarзнаходиться в основному файлі. Ось так: timeit.timeit('foobar()','from __main__ import foobar',number=1000)
Багатий

3
У Python 2.7.8 ви можете просто скористатисяtimeit.timeit( foobar, number=1000 )

9

Зосередьтеся на одній конкретній речі . Введення / виведення диска є повільним, тому я б вийняв це з тесту, якщо все, що ви збираєтеся налаштувати, це запит до бази даних.

І якщо вам потрібно вчасно виконати базу даних, шукайте натомість інструменти бази даних, як-от запит на план запитів, і зауважте, що продуктивність змінюється не лише в залежності від точного запиту та індексів, але і з завантаженням даних (скільки даних ви зберегли).

Однак, ви можете просто поставити код у функцію та запустити цю функцію за допомогою timeit.timeit():

def function_to_repeat():
    # ...

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

Це призведе до відключення збору сміття, багаторазового виклику function_to_repeat()функції та часу загальної тривалості тих дзвінків timeit.default_timer(), що використовуються , що є найбільш точним доступним годинником для вашої конкретної платформи.

Вам слід перемістити код налаштування з повторної функції; наприклад, спочатку слід підключитися до бази даних, а потім ввести лише запити. Використовуйте setupаргумент або імпортуйте, або створюйте ці залежності, і передайте їх у свою функцію:

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

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

хапав глобальний function_to_repeat, var1і var2від вашого сценарію і передати ті функції кожного повторення.


Введення коду у функцію - це крок, який я шукав - оскільки просто зробити код рядком і evaling не збирається летіти ні за що не зовсім тривіально. thx
javadba

2

Я бачу, що на питання вже відповіли, але все одно хочу додати свої 2 центи за те саме.

Я також зіткнувся з подібним сценарієм, в якому мені довелося перевірити терміни виконання декількох підходів і, отже, написав невеликий сценарій, який викликає timeit для всіх функцій, записаних у ньому.

Сценарій також доступний як github gist тут .

Сподіваємось, це допоможе вам та іншим.

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

Ось проста обгортка для відповіді Стівена. Ця функція не робить повторних пробіг / усереднення, просто позбавляє вас від необхідності повторювати код часу :)

'''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

Набір тестування не робить спроби використовувати імпортований, timeitтому важко сказати, у чому полягав намір. Тим не менш, це канонічна відповідь, тому повний приклад, timeitздається, в порядку, детальний опис відповіді Мартійна .

У документах дляtimeit пропонує багато прикладів і прапорів стоять перевірити. Основне використання в командному рядку:

$ 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

Запустіть, -hщоб переглянути всі варіанти. У Python MOTW є чудовий розділ, timeitде показано, як запускати модулі за допомогою імпорту та багаторядкових рядків коду з командного рядка.

У формі скрипту я його зазвичай використовую так:

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

Ви можете досить легко скинути потрібні функції та аргументи. Будьте обережні, використовуючи нечисті функції та дбайте про стан.

Вибірка зразка:

$ 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
------------------------------
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.