Skip to content

Commit ea782e5

Browse files
committed
14.13小节完成
1 parent 7988eb5 commit ea782e5

File tree

1 file changed

+134
-124
lines changed

1 file changed

+134
-124
lines changed
Lines changed: 134 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,158 @@
11
==============================
2-
14.13 给你的程序做基准测试
2+
14.13 给你的程序做性能测试
33
==============================
44

55
----------
66
问题
77
----------
8-
You would like to find out where your program spends its time and make timing
9-
measurements.
8+
9+
你想测试你的程序运行所花费的时间并做性能测试。
1010

1111
|
1212
1313
----------
1414
解决方案
1515
----------
16-
If you simply want to time your whole program, it’s usually easy enough to use something
17-
like the Unix time command. For example:
18-
19-
bash % time python3 someprogram.py
20-
real 0m13.937s
21-
user 0m12.162s
22-
sys 0m0.098s
23-
bash %
24-
25-
On the other extreme, if you want a detailed report showing what your program is doing,
26-
you can use the cProfile module:
27-
28-
bash % python3 -m cProfile someprogram.py
29-
859647 function calls in 16.016 CPU seconds
30-
31-
Ordered by: standard name
32-
33-
ncalls tottime percall cumtime percall filename:lineno(function)
34-
263169 0.080 0.000 0.080 0.000 someprogram.py:16(frange)
35-
513 0.001 0.000 0.002 0.000 someprogram.py:30(generate_mandel)
36-
262656 0.194 0.000 15.295 0.000 someprogram.py:32(<genexpr>)
37-
1 0.036 0.036 16.077 16.077 someprogram.py:4(<module>)
38-
262144 15.021 0.000 15.021 0.000 someprogram.py:4(in_mandelbrot)
39-
1 0.000 0.000 0.000 0.000 os.py:746(urandom)
40-
1 0.000 0.000 0.000 0.000 png.py:1056(_readable)
41-
1 0.000 0.000 0.000 0.000 png.py:1073(Reader)
42-
1 0.227 0.227 0.438 0.438 png.py:163(<module>)
43-
512 0.010 0.000 0.010 0.000 png.py:200(group)
16+
如果你只是简单的想测试下你的程序整体花费的时间,
17+
通常使用Unix时间函数就行了,比如:
18+
19+
::
20+
21+
bash % time python3 someprogram.py
22+
real 0m13.937s
23+
user 0m12.162s
24+
sys 0m0.098s
25+
bash %
26+
27+
如果你还需要一个程序各个细节的详细报告,可以使用 ``cProfile`` 模块:
28+
29+
::
30+
31+
bash % python3 -m cProfile someprogram.py
32+
859647 function calls in 16.016 CPU seconds
33+
34+
Ordered by: standard name
35+
36+
ncalls tottime percall cumtime percall filename:lineno(function)
37+
263169 0.080 0.000 0.080 0.000 someprogram.py:16(frange)
38+
513 0.001 0.000 0.002 0.000 someprogram.py:30(generate_mandel)
39+
262656 0.194 0.000 15.295 0.000 someprogram.py:32(<genexpr>)
40+
1 0.036 0.036 16.077 16.077 someprogram.py:4(<module>)
41+
262144 15.021 0.000 15.021 0.000 someprogram.py:4(in_mandelbrot)
42+
1 0.000 0.000 0.000 0.000 os.py:746(urandom)
43+
1 0.000 0.000 0.000 0.000 png.py:1056(_readable)
44+
1 0.000 0.000 0.000 0.000 png.py:1073(Reader)
45+
1 0.227 0.227 0.438 0.438 png.py:163(<module>)
46+
512 0.010 0.000 0.010 0.000 png.py:200(group)
47+
...
48+
bash %
49+
50+
不过通常情况是介于这两个极端之间。比如你已经知道代码运行时在少数几个函数中花费了绝大部分时间。
51+
对于这些函数的性能测试,可以使用一个简单的装饰器:
52+
53+
.. code-block:: python
54+
55+
# timethis.py
56+
57+
import time
58+
from functools import wraps
59+
60+
def timethis(func):
61+
@wraps(func)
62+
def wrapper(*args, **kwargs):
63+
start = time.perf_counter()
64+
r = func(*args, **kwargs)
65+
end = time.perf_counter()
66+
print('{}.{} : {}'.format(func.__module__, func.__name__, end - start))
67+
return r
68+
return wrapper
69+
70+
要使用这个装饰器,只需要将其放置在你要进行性能测试的函数定义前即可,比如:
71+
72+
::
73+
74+
>>> @timethis
75+
... def countdown(n):
76+
... while n > 0:
77+
... n -= 1
4478
...
45-
bash %
79+
>>> countdown(10000000)
80+
__main__.countdown : 0.803001880645752
81+
>>>
4682

47-
More often than not, profiling your code lies somewhere in between these two extremes.
48-
For example, you may already know that your code spends most of its time in a few
49-
selected functions. For selected profiling of functions, a short decorator can be useful.
50-
For example:
83+
要测试某个代码块运行时间,你可以定义一个上下文管理器,例如:
5184

52-
# timethis.py
85+
.. code-block:: python
5386
54-
import time
55-
from functools import wraps
87+
from contextlib import contextmanager
5688
57-
def timethis(func):
58-
@wraps(func)
59-
def wrapper(*args, **kwargs):
89+
@contextmanager
90+
def timeblock(label):
6091
start = time.perf_counter()
61-
r = func(*args, **kwargs)
62-
end = time.perf_counter()
63-
print('{}.{} : {}'.format(func.__module__, func.__name__, end - start))
64-
return r
65-
return wrapper
66-
67-
To use this decorator, you simply place it in front of a function definition to get timings
68-
from it. For example:
69-
70-
>>> @timethis
71-
... def countdown(n):
72-
... while n > 0:
73-
... n -= 1
74-
...
75-
>>> countdown(10000000)
76-
__main__.countdown : 0.803001880645752
77-
>>>
78-
79-
To time a block of statements, you can define a context manager. For example:
80-
81-
from contextlib import contextmanager
82-
83-
@contextmanager
84-
def timeblock(label):
85-
start = time.perf_counter()
86-
try:
87-
yield
88-
finally:
89-
end = time.perf_counter()
90-
print('{} : {}'.format(label, end - start))
91-
Here is an example of how the context manager works:
92-
93-
>>> with timeblock('counting'):
94-
... n = 10000000
95-
... while n > 0:
96-
... n -= 1
97-
...
98-
counting : 1.5551159381866455
99-
>>>
100-
101-
For studying the performance of small code fragments, the timeit module can be useful.
102-
For example:
103-
104-
>>> from timeit import timeit
105-
>>> timeit('math.sqrt(2)', 'import math')
106-
0.1432319980012835
107-
>>> timeit('sqrt(2)', 'from math import sqrt')
108-
0.10836604500218527
109-
>>>
110-
111-
timeit works by executing the statement specified in the first argument a million times
112-
and measuring the time. The second argument is a setup string that is executed to set
113-
up the environment prior to running the test. If you need to change the number of
114-
iterations, supply a number argument like this:
115-
116-
>>> timeit('math.sqrt(2)', 'import math', number=10000000)
117-
1.434852126003534
118-
>>> timeit('sqrt(2)', 'from math import sqrt', number=10000000)
119-
1.0270336690009572
120-
>>>
92+
try:
93+
yield
94+
finally:
95+
end = time.perf_counter()
96+
print('{} : {}'.format(label, end - start))
97+
98+
下面是使用这个上下文管理器的例子:
99+
100+
::
101+
102+
>>> with timeblock('counting'):
103+
... n = 10000000
104+
... while n > 0:
105+
... n -= 1
106+
...
107+
counting : 1.5551159381866455
108+
>>>
109+
110+
对于测试很小的代码片段运行性能,使用 ``timeit`` 模块会很方便,例如:
111+
112+
::
113+
114+
>>> from timeit import timeit
115+
>>> timeit('math.sqrt(2)', 'import math')
116+
0.1432319980012835
117+
>>> timeit('sqrt(2)', 'from math import sqrt')
118+
0.10836604500218527
119+
>>>
120+
121+
``timeit`` 会执行第一个参数中语句100万次并计算运行时间。
122+
第二个参数是运行测试之前配置环境。如果你想改变循环执行次数,
123+
可以像下面这样设置 ``number`` 参数的值:
124+
125+
::
126+
127+
>>> timeit('math.sqrt(2)', 'import math', number=10000000)
128+
1.434852126003534
129+
>>> timeit('sqrt(2)', 'from math import sqrt', number=10000000)
130+
1.0270336690009572
131+
>>>
121132

122133
|
123134
124135
----------
125136
讨论
126137
----------
127-
When making performance measurements, be aware that any results you get are ap‐
128-
proximations. The time.perf_counter() function used in the solution provides the
129-
highest-resolution timer possible on a given platform. However, it still measures wall-
130-
clock time, and can be impacted by many different factors, such as machine load.
131-
If you are interested in process time as opposed to wall-clock time, use time.pro
132-
cess_time() instead. For example:
133-
134-
from functools import wraps
135-
def timethis(func):
136-
@wraps(func)
137-
def wrapper(*args, **kwargs):
138-
start = time.process_time()
139-
r = func(*args, **kwargs)
140-
end = time.process_time()
141-
print('{}.{} : {}'.format(func.__module__, func.__name__, end - start))
142-
return r
143-
return wrapper
144-
145-
Last, but not least, if you’re going to perform detailed timing analysis, make sure to read
146-
the documentation for the time, timeit, and other associated modules, so that you have
147-
an understanding of important platform-related differences and other pitfalls.
148-
See Recipe 13.13 for a related recipe on creating a stopwatch timer class.
138+
当执行性能测试的时候,需要注意的是你获取的结果都是近似值。
139+
``time.perf_counter()`` 函数会在给定平台上获取最高精度的计时值。
140+
不过,它仍然还是基于时钟时间,很多因素会影响到它的精确度,比如机器负载。
141+
如果你对于执行时间更感兴趣,使用 ``time.process_time()`` 来代替它。例如:
142+
143+
.. code-block:: python
144+
145+
from functools import wraps
146+
def timethis(func):
147+
@wraps(func)
148+
def wrapper(*args, **kwargs):
149+
start = time.process_time()
150+
r = func(*args, **kwargs)
151+
end = time.process_time()
152+
print('{}.{} : {}'.format(func.__module__, func.__name__, end - start))
153+
return r
154+
return wrapper
155+
156+
最后,如果你想进行更深入的性能分析,那么你需要详细阅读 ``time`` 、``timeit`` 和其他相关模块的文档。
157+
这样你可以理解和平台相关的差异以及一些其他陷阱。
158+
还可以参考13.13小节中相关的一个创建计时器类的例子。

0 commit comments

Comments
 (0)