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å°�节ä¸ç›¸å…³çš„一个创建计时器类的例å�。