python计时模块
计时模块
简单的记录下python一些计时器。
1.timeit模块
timeit模块用来计时一些短的代码片段执行时间。该模块提供了两种使用方式,一种是命令行模式下直接调用,另一种就是当普通的模块调用。
命令行模式
yscl@yscl:~$ python3 -m timeit '"-".join(str(i) for i in range(100))' 10000 loops, best of 3: 19.2 usec per loop yscl@yscl:~$ python3 -m timeit '"-".join([str(i) for i in range(100)])' 100000 loops, best of 3: 17 usec per loop yscl@yscl:~$ python3 -m timeit '"-".join(map(str, range(100)))' 100000 loops, best of 3: 14 usec per loop
可以看出同样是字符串连接操作,当调用map函数花费时间最短。
交互模式
>>> timeit.timeit('"-".join(str(n) for n in range(100))', number=10000) 0.2559542650014919 >>> timeit.timeit('"-".join([str(n) for n in range(100)])', number=10000) 0.20574105799823883 >>> timeit.timeit('"-".join(map(str, range(100)))', number=10000) 0.1546621529996628
命令行模式还提供了一些可选参数,这里只列了3个最常用的,其他参考官方文档timeit.html
基础的python -m timeit
-n N, --number=N:指定执行的次数
-p P --repeat=N:定时器重复次数(只测量一次容易受到其他程序的影响)
-s S, --setup=S:对执行的代码进行初始化操作(常见的如导入自己的模块以测量自己写的函数运行时间)
timeit还有个常用函数就是repeat,使用方法同timeit.timeit,只是多了个定时器重复次数的参数。具体定义如下:
timeit.repeat(stmt='pass', setup='pass', timer=<default timer>, repeat=5, number=1000000, globals=None)
参数:stmt: 要执行的代码段,setup: 要执行代码的初始化, timer: 默认是time.perf_counter(), repeat: 定时器重复次数, number: 启动一次定时器代码执行的次数, globals: 指定代码执行的命名空间
功能:返回一个一个列表,是每一次定时器的测量的执行时间,一般程序运行会受到其他程序的影响,所以可以关注那个最小的值。
import timeit # 获得倒序的列表 code_append = """ lst = [] for i in range(100): lst.append(i) lst = lst[::-1] """ code_insert = """ lst = [] for i in range(100): lst.insert(0, i) """ print(timeit.repeat(code_append, number=10000)) print(timeit.repeat(code_insert, number=10000))
[0.0996780889981892, 0.08902960799605353, 0.0855226429994218] [0.18702545399719384, 0.19733670599816833, 0.16741226500016637]
此处可以看出列表append操作速度远比插入速度快,至于只重复了3次,这是我的Python版本是3.5,在3.7版本默认重复5次。
再来一个使用初始化参数的版本,用来测量自定义的函数的执行时间
>>> import timeit >>> timeit.timeit('func()', setup='from test_timeit import func') 6.636295215001155 >>> timeit.repeat('func()', setup='from test_timeit import func') [6.601007812001626, 6.5381397309975, 6.733538911998039]
2. profilers
python提供了cProfile和profile两个模块进行程序性能分析。cProfile是一个c扩展,运行效率更高,开销也合理,适合长期运行的程序。而profile是用纯python实现的,但是开销明显增加了,使用方法两者都是一样的。
简单使用
import cProfile import re cProfile.run('re.compile(r"\d+[abcde]")', sort='cumulative')
运行字符串里的代码后输出如下
160 function calls (157 primitive calls) in 0.000 seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 {built-in method builtins.exec} 1 0.000 0.000 0.000 0.000 <string>:1(<module>) 1 0.000 0.000 0.000 0.000 re.py:222(compile) 1 0.000 0.000 0.000 0.000 re.py:278(_compile) 1 0.000 0.000 0.000 0.000 sre_compile.py:531(compile) 1 0.000 0.000 0.000 0.000 sre_parse.py:819(parse)
2/1 0.000 0.000 0.000 0.000 sre_compile.py:64(_compile)
第一行是说明当前有160个函数被调用,其中157个是正常调用(不是递归引发的调用),第二行是按照总共累计运行时间从大到小排序的(默认是按照名称排序),每列的标题含义如下:
ncalls:函数调用的次数。其中(2/1)表示函数是递归的,前面的2表示递归总次数,1表示原始调用
tottime:函数花费的总时间,不包括子函数的调用时间
percall:每次调用的平均时间(tottime/ncalls)
cumtime:函数执行累计的总时间(包括子函数调用的时间和递归调用的时间)
percall:每次调用的平均时间(cumtime/calls)
filename:lineno(function):函数所在文件的名字和函数执行所在行数
profile模块还提供将性能分析信息保存到文件中,使用也很方便,只要在run函数指定文件名即可,然后便可以用pstats模块从文件读取分析,并以各种方式格式化显示出来。
以下是一个例子。用来分析自己写的程序,该程序功能是简单统计N以内的素数对(即相差为2,且都为素数)的总数。
# prime_pair.py
import math def is_prime(num): if num == 1: return False elif num == 2 or num == 3: return True elif num % 2 == 0: return False n = int(math.sqrt(num)) for i in range(3, n + 1, 2): if not(num % i): return False return True def main(): # num = int(input()) num = 100000 count = 0 n = 3 while n < num - 1: if is_prime(n): while is_prime(n + 2) and n + 2 <= num: count += 1 n += 2 n += 2 n += 2 print(count) if __name__ == '__main__': main()
import cProfile cProfile.run('from prime_pair import main; main()', sort='cumulative', filename='restats')
执行上述代码,即可产生一个名为restats的文件,然后可以对该文件进行分析。
import pstats p = pstats.Stats('restats') p.strip_dirs().sort_stats('cumtime').print_stats(10)
1000215 function calls (1000209 primitive calls) in 3.044 seconds Ordered by: cumulative time List reduced from 93 to 10 due to restriction <10> ncalls tottime percall cumtime percall filename:lineno(function) 2/1 0.001 0.000 3.044 3.044 {built-in method builtins.exec} 1 0.000 0.000 3.044 3.044 <string>:1(<module>) 1 0.158 0.158 3.044 3.044 prime_pair.py:19(main) 499998 2.785 0.000 2.886 0.000 prime_pair.py:4(is_prime) 499997 0.101 0.000 0.101 0.000 {built-in method math.sqrt} 2/1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:966(_find_and_load) 2/1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:939(_find_and_load_unlocked) 2/1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:659(_load_unlocked) 1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap_external>:659(exec_module) 2 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:879(_find_spec)
上面代码中,首先创建一个stats统计对象,执行strip_dirs()方法可以去掉与模块无关的路径名称,sort_stats('cumtime')方法可以按照指定关键字对结果进行排序,print_stats(10)打印符合条件的前10条。然后根据结果分析来看,我的程序几乎所有时间都用在判断素数上了,所以优化素数判断的算法,就能优化整个程序了。关于pstats.Stats 类的详细方法查看官方文档
最后提一句,profile还支持在命令行模式直接执行。
python -m cProfile -s 'ncalls' prime_pair.py
1224
149995 function calls in 0.206 seconds
Ordered by: call count
ncalls tottime percall cumtime percall filename:lineno(function)
49998 0.158 0.000 0.189 0.000 prime_pair.py:4(is_prime)
49997 0.007 0.000 0.007 0.000 {math.sqrt}
49997 0.024 0.000 0.024 0.000 {range}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
1 0.000 0.000 0.206 0.206 prime_pair.py:1(<module>)
1 0.017 0.017 0.206 0.206 prime_pair.py:19(main)
命令行模式还有一个可选参数,就是-o,指定输出的文件名字,-s是指定排序方式。
参考:

浙公网安备 33010602011771号