Files
python3-cookbook/source/c14/p13_profiling_and_timing_your_program.rst
2015-12-28 19:34:04 +08:00

155 lines
4.8 KiB
ReStructuredText
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

==============================
14.13 给你的程序做性能测试
==============================
----------
问题
----------
你想测试你的程序运行所花费的时间并做性能测试。
----------
解决方案
----------
如果你只是简单的想测试下你的程序整体花费的时间,
通常使用Unix时间函数就行了比如
::
bash % time python3 someprogram.py
real 0m13.937s
user 0m12.162s
sys 0m0.098s
bash %
如果你还需要一个程序各个细节的详细报告,可以使用 ``cProfile`` 模块:
::
bash % python3 -m cProfile someprogram.py
859647 function calls in 16.016 CPU seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
263169 0.080 0.000 0.080 0.000 someprogram.py:16(frange)
513 0.001 0.000 0.002 0.000 someprogram.py:30(generate_mandel)
262656 0.194 0.000 15.295 0.000 someprogram.py:32(<genexpr>)
1 0.036 0.036 16.077 16.077 someprogram.py:4(<module>)
262144 15.021 0.000 15.021 0.000 someprogram.py:4(in_mandelbrot)
1 0.000 0.000 0.000 0.000 os.py:746(urandom)
1 0.000 0.000 0.000 0.000 png.py:1056(_readable)
1 0.000 0.000 0.000 0.000 png.py:1073(Reader)
1 0.227 0.227 0.438 0.438 png.py:163(<module>)
512 0.010 0.000 0.010 0.000 png.py:200(group)
...
bash %
不过通常情况是介于这两个极端之间。比如你已经知道代码运行时在少数几个函数中花费了绝大部分时间。
对于这些函数的性能测试,可以使用一个简单的装饰器:
.. code-block:: python
# timethis.py
import time
from functools import wraps
def timethis(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
r = func(*args, **kwargs)
end = time.perf_counter()
print('{}.{} : {}'.format(func.__module__, func.__name__, end - start))
return r
return wrapper
要使用这个装饰器,只需要将其放置在你要进行性能测试的函数定义前即可,比如:
::
>>> @timethis
... def countdown(n):
... while n > 0:
... n -= 1
...
>>> countdown(10000000)
__main__.countdown : 0.803001880645752
>>>
要测试某个代码块运行时间,你可以定义一个上下文管理器,例如:
.. code-block:: python
from contextlib import contextmanager
@contextmanager
def timeblock(label):
start = time.perf_counter()
try:
yield
finally:
end = time.perf_counter()
print('{} : {}'.format(label, end - start))
下面是使用这个上下文管理器的例子:
::
>>> with timeblock('counting'):
... n = 10000000
... while n > 0:
... n -= 1
...
counting : 1.5551159381866455
>>>
对于测试很小的代码片段运行性能,使用 ``timeit`` 模块会很方便,例如:
::
>>> from timeit import timeit
>>> timeit('math.sqrt(2)', 'import math')
0.1432319980012835
>>> timeit('sqrt(2)', 'from math import sqrt')
0.10836604500218527
>>>
``timeit`` 会执行第一个参数中语句100万次并计算运行时间。
第二个参数是运行测试之前配置环境。如果你想改变循环执行次数,
可以像下面这样设置 ``number`` 参数的值:
::
>>> timeit('math.sqrt(2)', 'import math', number=10000000)
1.434852126003534
>>> timeit('sqrt(2)', 'from math import sqrt', number=10000000)
1.0270336690009572
>>>
----------
讨论
----------
当执行性能测试的时候,需要注意的是你获取的结果都是近似值。
``time.perf_counter()`` 函数会在给定平台上获取最高精度的计时值。
不过,它仍然还是基于时钟时间,很多因素会影响到它的精确度,比如机器负载。
如果你对于执行时间更感兴趣,使用 ``time.process_time()`` 来代替它。例如:
.. code-block:: python
from functools import wraps
def timethis(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.process_time()
r = func(*args, **kwargs)
end = time.process_time()
print('{}.{} : {}'.format(func.__module__, func.__name__, end - start))
return r
return wrapper
最后,如果你想进行更深入的性能分析,那么你需要详细阅读 ``time````timeit`` 和其他相关模块的文档。
这样你可以理解和平台相关的差异以及一些其他陷阱。
还可以参考13.13小节中相关的一个创建计时器类的例子。