# timer.py 文件 import time, sys timer = time.clock if sys.platform[:3] == 'win'else time.time
deftotal(reps, func, *pargs, **kargs): """ Total time to run func() reps times. Return (total time, last result) """ repslist = list(range(reps)) start = timer() for i in repslist: ret = func(*pargs, **kargs) elapsed = timer() - start return (elapsed, ret)
defbestof(reps, func, *pargs, **kargs): """ Quickest func() among reps runs. Returns (best time, last result) """ best = 2 ** 32 for i inrange(reps): # range 在这里不被计时 start = timer() ret = func(*pargs, **kargs) elapsed = timer() - start if elapsed < best: best = elapsed return (best, ret)
defbestoftotal(reps1, reps2, func, *pargs, **kargs): """ Best of totals: (best of reps1 runs of (total of reps2 runs of func)) """ return bestof(reps1, total, reps2, func, *pargs, **kargs)
这个版本解决最初版本缺点的一些要点:
Python 的 time 模块获取当前的时间,精度随着每个平台而有所不同。在 Windows 上的 clock 函数调用能够达到微秒的精度。因为 time 函数在 Unix 上可能更好,这个脚本通过平台自动选择函数。
range 调用在 total 函数中被放到了计时循环之外,因此,它的构建成本不会计算到 Python 2.X 的计时函数中,在 Python 3.X 的 range 是一个迭代器,因此这个步骤是不需要也无害的。
# timer2.py 文件 """ total(spam, 1, 2, a=3, b=4, _reps=1000) calls and times spam(1, 2, a=3, b=4) _reps times, and return total time for all runs, with final result. bestof(spam, 1, 2, a=3, b=4, _reps=5) runs best-of-N timer to attempt to filter out system load variation, and returns best time among _reps tests. bestoftotal(spam, 1, 2, a=3, b=4, _reps1=5, reps=1000) runs best-of-totals test, which takes the best among _reps1 runs of (the total of _reps runs); """
import time, sys timer = time.clock if sys.platform[:3] == 'win'else time.time
deftotal(func, *pargs, **kargs): _reps = kargs.pop('_reps', 1000) # 传入或者默认 reps repslist = list(range(_reps)) start = timer() for i in repslist: ret = func(*pargs, **kargs) elapsed = timer() - start return (elapsed, ret)
defbestof(func, *pargs, **kargs): _reps = kargs.pop('_reps', 5) best = 2 ** 32 for i inrange(_reps): start = timer() ret = func(*pargs, **kargs) elapsed = timer() - start if elapsed < best: best = elapsed return (best, ret)
defbestoftotal(func, *pargs, **kargs): _reps1 = kargs.pop('_reps1', 5) returnmin(total(func, *pargs, **kargs) for i inrange(_reps1))
# timer3.py 文件 """ Same usage as timer2.py, but uses 3.X keyword-only default arguments instead of dict pops for simpler code. No need to hoist range() out of tests in 3.X: always a generator in 3.X, and this can't run on 2.X.""" import time, sys timer = time.clock if sys.platform[:3] == 'win'else time.time
deftotal(func, *pargs, _reps=1000, **kargs): start = timer() for i inrange(_reps): ret = func(*pargs, **kargs) elapsed = timer() - start return (elapsed, ret)
defbestof(func, *pargs, _reps=5, **kargs): best = 2 ** 32 for i inrange(_reps): start = timer() ret = func(*pargs, **kargs) elapsed = timer() - start if elapsed < best: best = elapsed return (best, ret)
defbestoftotal(func, *pargs, _reps1=5, **kargs): returnmin(total(func, *pargs, **kargs) for i inrange(_reps1))
2. 使用 timeit 对迭代和 Python 进行计时
标准函数提供了一个 timeit 模块对函数进行计时。
2.1 基本 timeit 用法
使用 timeit,测试方法由可调用对象或者语句字符串指定;对于后者,可以使用 ; 分隔符或者 \n 换行符来执行多个语句,使用空格或制表符在嵌套块中缩进语句。测试可以通过命令行和 API 调用执行。
交互式用法和 API 调用
1 2
import timeit min(timeit.repeat(stmt='[x ** 2 for x in range(1000)]', number=1000, repeat=5))
0.2696741744839528
命令行用法
在这个模式中,timeit 报告 n 次循环的平均用时,单位为微秒(usec)、毫秒(msec)或秒(sec)。
1
!E:\Anaconda\Lib\timeit.py -n 1000"[x ** 2 for x in range(1000)]"
1
!Python -m timeit -n 1000"[x ** 2 for x in range(1000)]"
1000 loops, best of 3: 262 usec per loop
1
!Python -m timeit -n 1000 -r 5"[x ** 2 for x in range(1000)]"
1000 loops, best of 5: 274 usec per loop
对多行语句计时
使用换行符、制表符或者空格来满足 Python 的语法。
1 2 3
import timeit min(timeit.repeat(number=10000, repeat=3, stmt='L = [1, 2, 3, 4, 5]\nfor i in range(len(L)): L[i] += 1'))
""" pybench.py: Test speed of one or more Pythons on a set of simple code-string benchmarks. A function, to allow stmts to vary. This system itself runs on both 2.X and 3.X, and may spawn both. Uses timeit to test either the Python running this script by API calls, or a set of Pythons by reading spawned command-line outputs (os.popen) with Python's -m flag to find timeit on module search path. Replaces $listif3 with a list() around generators for 3.X and an empty string for 2.X, so 3.X does same work as 2.X. In command-line mode only, must split multiline statements into one separate quoted argument per line so all will be run (else might run/time first line only), and replace all \t in indentation with 4 spaces for uniformity. Caveats: command-line mode (only) may fail if test stmt embeds double quotes, quoted stmt string is incompatible with shell in general, or command-line exceeds a length limit on platform's shell--use API call mode or homegrown timer; does not yet support a setup statement: as is, time of all statements in the test stmt are charged to the total time. """
import sys, os, timeit defnum, defrep = 1000, 5
defrunner(stmts, pythons=None, tracecmd=False): """ Main logic: run tests per input lists, caller handles usage modes. stmts: [(number?, repeat?, stmt-string)], replaces $listif3 in stmt pythons: None=this python only, or [(ispy3?, python-executable-path)] """ print(sys.version) for (number, repeat, stmt) in stmts: number = number or defnum repeat = repeat or defrep ifnot pythons: # Run stmt on this python: API call # No need to split lines or quote here ispy3 = sys.version[0] == '3' stmt = stmt.replace('$listif3', 'list'if ispy3 else'') best = min(timeit.repeat(stmt=stmt, number=number, repeat=repeat)) print('%.4f [%r]' % (best, stmt[:70])) else: # Run stmt on all pythons: command line # Split lines into quoted arguments print('-' * 80) print('[%r]' % stmt) for (ispy3, python) in pythons: stmt1 = stmt.replace('$listif3', 'list'if ispy3 else'') stmt1 = stmt1.replace('\t', ' ' * 4) lines = stmt1.split('\n') args = ' '.join('"%s"' % line for line in lines) cmd = '%s -m timeit -n %s -r %s %s' % (python, number, repeat, args) print(python) if tracecmd: print(cmd) print('\t' + os.popen(cmd).read().rstrip())
""" pybench_cases.py: Run pybench on a set of pythons and statements. Select modes by editing this script or using command-line arguments (in sys.argv): e.g., run a "C:\python27\python pybench_cases.py" to test just one specific version on stmts, "pybench_cases.py -a" to test all pythons listed, or a "py −3 pybench_cases.py -a -t" to trace command lines too. """ import pybench, sys pythons = [(1, "E:\Anaconda")] # (ispy3?, path) stmts = [ # (num,rpt,stmt) (0, 0, "[x ** 2 for x in range(1000)]"), # Iterations (0, 0, "res=[]\nfor x in range(1000): res.append(x ** 2)"), # \n=multistmt (0, 0, "$listif3(map(lambda x: x ** 2, range(1000)))"), # \n\t=indent (0, 0, "list(x ** 2 for x in range(1000))"), # $=list or '' (0, 0, "s = 'spam' * 2500\nx = [s[i] for i in range(10000)]"), # String ops (0, 0, "s = '?'\nfor i in range(10000): s += '?'"), ] tracecmd = '-t'in sys.argv # -t: trace command lines? pythons = pythons if'-a'in sys.argv elseNone# -a: all in list, else one? pybench.runner(stmts, pythons, tracecmd)
3.6.5 |Anaconda, Inc.| (default, Mar 29 2018, 13:32:41) [MSC v.1900 64 bit (AMD64)]
0.2585 ['[x ** 2 for x in range(1000)]']
0.2967 ['res=[]\nfor x in range(1000): res.append(x ** 2)']
0.3181 ['list(map(lambda x: x ** 2, range(1000)))']
0.2980 ['list(x ** 2 for x in range(1000))']
0.4162 ["s = 'spam' * 2500\nx = [s[i] for i in range(10000)]"]
2.5342 ["s = '?'\nfor i in range(10000): s += '?'"]