在实现了需求之后,若想使运行速度提高,则需要先对代码整体的运行时间做一个分析,找出瓶颈在何处。“二八原则”在这里也是适用的,即20%的代码占了80%的运行时间。
这里介绍pyhton中两个实现此功能的函数。
cProfile
简介
profile是Python的标准库。可以统计程序里每一个函数的运行时间,并且提供了多样化的报表,而cProfile则是它的C实现版本,剖析过程本身需要消耗的资源更少。所以在Python 3中,cProfile代替了profile,成为默认的性能剖析模块
使用
它的实现也很简单,以下面这段代码为例:
import cProfile
import time
def fun1():
sum = 0
for i in range(1000000):
sum += i
def fun2():
time.sleep(10)
def main():
fun1()
fun2()
cProfile.run('main()') # 注意这里的''
输出:
7 function calls in 10.044 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 10.044 10.044 -11-b3eeba351a69>:10(main)1 0.045 0.045 0.045 0.045 -11-b3eeba351a69>:3(fun1)1 0.000 0.000 9.999 9.999 -11-b3eeba351a69>:7(fun2)1 0.000 0.000 10.044 10.044 :1()1 0.000 0.000 10.044 10.044 {built-in method builtins.exec}1 9.999 9.999 9.999 9.999 {built-in method time.sleep}1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
上面显示了函数的运行时间情况,可以看出fun2
及其里面的time.sleep
函数运行时间占用时间较多。
也可以在python解释器调用cProfile来分析代码运行时间,如输入以下命令,效果一样。
python -m cProfile prof1.py
输出的各项意义如下:
但是结果不方便我们查看,特别是函数特别多的时候。这个时候可以结合pstats模块的一个类Stats来使用。
Stats函数接受一个参数——就是cProfile的输出文件名。Stats提供了对cProfile输出结果进行排序、输出控制等功能。如我们把前文的程序改为如下:
# 省略前面的部分
def main():
fun1()
fun2()
cProfile.run('main()', 'prof.txt')
import pstats
p = pstats.Stats('prof.txt')
p.sort_stats('time').print_stats()
引入pstats之后,把结果按照运行时间排序,结果如下:
Thu Jul 30 13:30:57 2020 prof.txt
7 function calls in 10.043 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
1 10.001 10.001 10.001 10.001 {built-in method time.sleep}
1 0.043 0.043 0.043 0.043 -11-b3eeba351a69>:3(fun1)1 0.000 0.000 10.043 10.043 {built-in method builtins.exec}1 0.000 0.000 10.001 10.001 -11-b3eeba351a69>:7(fun2)1 0.000 0.000 10.043 10.043 -11-b3eeba351a69>:10(main)1 0.000 0.000 10.043 10.043 :1()1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Stats有若干个函数,这些函数组合能输出不同的cProfile报表,功能非常强大,其中最重要的函数就是sort_stats和print_stats,通过这两个函数我们几乎可以用适当的形式浏览所有的信息了。下面是sort_stats支持的排序参数。
上面介绍的cProfile主要是针对较多的代码,若是想测试一下单行或者函数的耗时,用下面这个库实现会简单很多。
timeit
- 测试单行语句
from timeit import timeit
timeit('"-".join(str(n) for n in range(100))', number=10000)
output: 0.3018611848820001
其中,number
参数表示代码的运行次数。
- 测试多行语句
from timeit import timeit
string = """
s = 0
for i in range(1000):
s += i
"""
timeit(stmt=string,number=100)
- 测试函数
from timeit import timeit
def func():
s = 0
for i in range(1000):
s += i
print(s)
# timeit(函数名_字符串,运行环境_字符串,number=运行次数)
t = timeit('func()', 'from __main__ import func', number=1000)
print(t)
output: 0.04860229999758303
说到timeit
模块,不得不先提一下它简便的魔术命令实现。
魔法函数: %timeit
,它会自动计算接下来一行的 Python 语句的执行时间。例如,我们可能想了解列表综合的性能:
%timeit L = [n ** 2 for n in range(1000)]
output: 273 µs ± 1.51 µs per loop
(mean ± std. dev. of 7 runs, 1000 loops each)
对于多行语句,可以加入第二个 %
符号将其转变成单元魔法,以处理多行输入。例如,下面是 上面for循环的同等结构:
%%timeit
L = []
for n in range(1000):
L.append(n**2)
output: 299 µs ± 1.72 µs per loop
(mean ± std. dev. of 7 runs, 1000 loops each)
以上就是关于Python中代码计时的一些分享,最后分享一句话。
过早优化是编程中一切“罪恶”的根源。——Donald Knuth
在后续的一些优化技巧中会发现,过早的优化会让你的努力得不到应有的回报。
如果有任何问题或者本文有写的不对的地方,欢迎点击下方蓝字留言。
点此留言