利用 cProfile 进行 Python 程序性能调优

在上一篇文章中,我提到过 cProfile 对 Python 进行调优,但是仅仅只是简单的一笔带过,这篇文章就针对这个内容,单独扩展一下。cProfile 是 Python 的性能测试工具,另外一个同类工具是 python 实现的 profile,不过 cProfile 是 C 语言扩展的实现。

官方文档页面:http://docs.python.org/2/library/profile.html#module-cProfile

先从一个例子开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import time


def delay():
time.sleep(10)


def main():
delay()
print "123"

if __name__ == '__main__':
main()

使用下面这个命令就可以快速诊断:

python -m cProfile profile_run.py

输出如下:

     5 function calls in 10.002 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.002    0.002   10.002   10.002 profile_run.py:11(main)
    1    0.000    0.000   10.002   10.002 profile_run.py:4(<module>)
    1    0.000    0.000   10.000   10.000 profile_run.py:7(delay)  <-- 调用者这里
    1    0.000    0.000    0.000    0.000 {method'disable'of'_lsprof.Profiler'objects}
    1   10.000   10.000   10.000   10.000 {time.sleep}   <--- 元凶在这里

如果想输出到文件,添加一个 - o 参数即可:

python -m cProfile -o profile_output profile_run.py

若想按照某列排序,则可以添加这个参数:

python -m cProfile -o profile_output -s tottime profile_run.py

那么等等,这个结果文件如何识别呢:

ncalls: 这个函数被调用的次数

tottime:函数的具体执行时间,不包含其他函数消耗的时间

percall:每次调用的消耗(包含其他函数消耗和不包含的时间)

cumtime:程序消耗的总时间

然后看一个实际的成果看一下结果如何,具体的诊断数据:

cProfile Result

发现最慢的执行模块是 not_operation 这个函数:

1
2
3
4
5
6
7
8
9
10
def not_operation(operand, dictionary, pfile):
"""Performs the operation `NOT operand`."""

# A list of all the documents (sorted)
all_docs = dictionary.all_docs()

# A list of the documents matching `operand` (sorted)
results = get_results(operand, dictionary, pfile, force_list=True)

return [doc for doc in all_docs if doc not in results]

接下来是怎么修复了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# the fix.
def not_operation(operand, dictionary, pfile):
"""Performs the operation `NOT operand`."""

# A list of all the documents (sorted)
all_docs = dictionary.all_docs()

# A list of the documents matching `operand` (sorted)
results = get_results(operand, dictionary, pfile, force_list=True)

return list_a_and_not_list_b(all_docs, results)


def list_a_and_not_list_b(a, b):
"""Returns `a AND NOT b`.

Both a and b are expected to be sorted lists.

"""results = []
idx_a = 0
idx_b = 0
while idx_a <len(a) and idx_b <len(b):
if a[idx_a] <b[idx_b]:
results.append(a[idx_a])
idx_a += 1
elif b[idx_b] <a[idx_a]:
idx_b += 1
else:
idx_a += 1
idx_b += 1

while idx_a <len(a):
results.append(a[idx_a])
idx_a += 1

对了,说起来,上面图片查看工具是 cprofilev,是针对 cProfile 的查看工具,使用命令安装:

pip install bottle cprofilev

cprofilev profile_output

如果在 Windows 安装默认有点问题,你如果想解决,可以自己看一下:)