如何使用 Pythons timeit 计时代码段来测试性能?
- 2025-01-06 08:32:00
- admin 原创
- 139
问题描述:
我有一个 python 脚本,它运行正常,但我需要写入执行时间。我在 Google 上搜索过我应该使用什么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
" % r
ibm_db.execute(query_stmt)
query_stmt = ibm_db.prepare(conn, update)
myfile.close()
ibm_db.close(conn)
我需要的是执行查询并将其写入文件所需的时间results_update.txt
。目的是使用不同的索引和调整机制测试我的数据库的更新语句。
解决方案 1:
您可以在想要计时的区块之前或之后使用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()
。
解决方案 2:
如果您正在分析您的代码并且可以使用 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
解决方案 3:
除了时间之外,您展示的代码完全是不正确的:您执行了 100 个连接(完全忽略了除最后一个之外的所有连接),然后当您执行第一次执行调用时,您将一个局部变量传递给它query_stmt
,您只在执行调用后初始化该变量。
首先,确保代码正确,无需担心时间问题:即,建立或接收连接并在该连接上执行 100 或 500 或任意数量的更新,然后关闭连接。一旦您的代码正常工作,就可以考虑使用timeit
它了!
具体来说,如果您想要计时的函数是无参数的函数,foobar
则可以使用timeit.timeit(2.6 或更高版本 - 在 2.5 及之前版本中更为复杂):
timeit.timeit('foobar()', number=1000)
从 3.5 开始,该globals
参数可以直接timeit
与接受参数的函数一起使用:
timeit.timeit('foobar(x,y)', number=1000, globals = globals())
您最好指定运行次数,因为默认值一百万次对于您的用例来说可能太高(导致在此代码上花费大量时间;-)。
解决方案 4:
专注于一件具体的事情。磁盘 I/O 很慢,所以如果您要调整的只是数据库查询,我会将其从测试中剔除。
如果您需要计时数据库执行时间,请寻找数据库工具,例如询问查询计划,并注意性能不仅因确切查询和索引而异,还因数据负载(您存储了多少数据)而异。
也就是说,您可以简单地将代码放入函数中并使用以下命令运行该函数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
在每次重复时将它们传递给函数。
解决方案 5:
这是 steven 答案的简单包装。此功能不执行重复运行/平均,只是让您不必到处重复计时代码 :)
'''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))
解决方案 6:
如何使用以下方法对函数进行计时timeit
:
import timeit
def time_this():
return 'a' + 'b'
timeit.timeit(time_this, number=1000)
它返回运行 1000 次所花费的时间(以秒为单位)time_this()
。
解决方案 7:
测试套件没有尝试使用导入的timeit
,因此很难判断其意图。尽管如此,这是一个规范的答案,因此完整的示例timeit
似乎是有序的,详细说明了Martijn 的答案。
的文档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}
n = {n}, {trials} trials
{'-' * 30}
")
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}
{'-' * 30}
")
您可以轻松添加所需的函数和参数。使用非纯函数时请谨慎,并注意状态。
示例输出:
$ 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
------------------------------
解决方案 8:
另一个简单的 timeit 示例:
def your_function_to_test():
# do some stuff...
time_to_run_100_times = timeit.timeit(your_function_to_test, number=100)
解决方案 9:
我看到这个问题已经得到解答了,但我还是想发表一下我的看法。
我也遇到过类似的情况,我必须测试几种方法的执行时间,因此编写了一个小脚本,该脚本在其中编写的所有函数上调用 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();"
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")
解决方案 10:
如果要测量性能,建议使用它time.perf_counter_ns()
,因为它具有纳秒精度。一个简单的例子:
import time
from time import perf_counter_ns
def convert_nanoseconds_to_seconds(nanoseconds: int) -> float:
return nanoseconds / 1_000_000_000
start = perf_counter_ns()
def my_function():
# Insert your code here
time.sleep(1)
end = perf_counter_ns()
print(f"Function took {convert_nanoseconds_to_seconds(end - start):.2f} seconds to execute.")
它在终端中的显示方式:
Function took 1.23 seconds to execute.
函数装饰器
但是,使用函数装饰器会更优雅@measure_performance
,因为每次调用函数时都会触发计时器,并且函数完成后时钟会自动停止。例如:
import time
from time import perf_counter_ns
def convert_nanoseconds_to_seconds(nanoseconds: int) -> float:
return nanoseconds / 1_000_000_000
def measure_performance(function):
def wrapper(*args, **kwargs):
start = perf_counter_ns()
result = function(*args, **kwargs)
end = perf_counter_ns()
print(f"Function {function.__name__} took {convert_nanoseconds_to_seconds(end - start):.2f} seconds to execute.")
return result
return wrapper
@measure_performance
def my_function():
# Insert your code here
time.sleep(1)
my_function()
Python 的带计时器的函数装饰器
另一个选择是使用现有的包。坦白说,我是Timer for Python的作者。这是一个轻量级的包,可以轻松测量时间和性能。它也基于time.perf_counter_ns()
,并且已经有一个内置的函数装饰器,名为@function_timer()
:
from timer import function_timer
@function_timer()
def my_function():
# Insert your code here
time.sleep(1)
my_function()
它在终端中的显示方式:
Elapsed time: 1.23 seconds for thread MY_FUNCTION
如果要自定义默认输出,可以设置不同的线程名称或小数。例如:
Elapsed time: 0.12345 seconds for thread CUSTOM