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 %

�过通常情况是介于这两个�端之间。比如你已�知�代��行时在少数几个函数中花费了�大部分时间。 对于这些函数的性能测试,�以使用一个简�的装饰器:

# 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
>>>

�测试�个代���行时间,你�以定义一个上下文管�器,例如:

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() �代替它。例如:

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�节中相关的一个创建计时器类的例�。