Как я могу рассчитать сегмент кода для тестирования производительности с помощью 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. Цель состоит в том, чтобы протестировать оператор обновления для моей базы данных с различными индексами и механизмами настройки.

Mestika
источник
Был ли ваш вопрос конкретным timeit? Я думаю, нет. В этом случае вам, вероятно, следует удалить «with time of 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 выполняет это автоматически, когда вы устанавливаете аргумент number )

import time

def myfast():
   code

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

total_n = t1-t0

В Windows, как сказал Кори в комментарии, time.clock()точность гораздо выше (микросекунда вместо секунды) и предпочтительнее time.time().

Хоакин
источник
8
к примеру, в Windows используйте time.clock () вместо time.time ()
Кори Голдберг
4
Спасибо Кори, почему? потому что часы точнее (микросекунды) или есть что-то еще?
Хоакин
11
Вы можете использовать timeit.default_timer (), чтобы сделать вашу платформу независимой; он возвращает time.clock () или time.time () в зависимости от операционной системы.
Марк Стобер,
6
Вместо того чтобы выбирать часы вручную, используйте timeit.default_timer; Python уже сделал всю работу за вас. Но на самом деле вы должны использовать timeit.timeit(myfast, number=n)вместо повторного изобретения колеса повторяющихся вызовов (и упустить тот факт, что timeitотключает сборщик мусора при повторном запуске кода).
Мартин Питерс
15
обновление: time.clock () больше не поддерживается. Теперь вы должны использовать time.time (). На самом деле, начиная с версии 3.3, лучшим вариантом будет time.perf_counter ()
Мадлозоз
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 соединений (полностью игнорируя все, кроме последнего), а затем, когда вы делаете первый вызов execute, вы передаете ему локальную переменную, query_stmtкоторую вы инициализируете только после execute вызов.

Во-первых, исправьте ваш код, не беспокоясь о времени: то есть функция, которая устанавливает или получает соединение и выполняет 100 или 500 или любое другое количество обновлений для этого соединения, а затем закрывает соединение. Как только ваш код работает правильно, вы должны подумать об использованииtimeit этом!

В частности, если вы хотите foobarиспользовать функцию time без вызова параметров, вы можете использовать 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 не собирается летать ни за что не совсем тривиально. ТНХ
javadba
2

Я вижу, что на вопрос уже дан ответ, но все же хочу добавить мои 2 цента за то же самое.

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

Сценарий также доступен как GitHub суть здесь .

Надеюсь, это поможет вам и другим.

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))
information_interchange
источник
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
------------------------------
ggorlen
источник
0

Еще один простой пример:

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

time_to_run_100_times = timeit.timeit(lambda: your_function_to_test, number=100)
Сэм
источник