您的位置:首页 > 编程语言 > Python开发

7个测量Python脚本和控制内存以及CPU使用率的技巧

2016-12-12 14:44 501 查看
本文的作者是Marina Mele,原文地址是 7 tips to Time Python scripts and control Memory & CPU usage

当运行一个复杂的 Python 程序,它需要很长时间来执行。你或许想提升它的执行时间。但如何做?

首先,你需要工具来查明你代码的瓶颈,比如,那部分执行花费的时间长。用这个方法,你可以首先专注于提升这部分的速度。

而且,你也应该控制内存和 CPU 使用率,因为它可以为你指出的代码可以改进的新的部分。

所以,在本文中,我将对 7 个不同的 Python 工具发表意见,给你一些关于你函数执行时间和内存以及 CPU 使用率的见解。

1、使用一个装饰器来测量你的函数

测量一个函数最简单的方式就是定义一个装饰器来测量运行该函数的运行时间,并打印该结果:

import time
from functools import wraps

def fn_timer(function):
@wraps(function)
def function_timer(*args, **kwargs):
t0 = time.time()
result = function(*args, **kwargs)
t1 = time.time()
print ("Total time running %s: %s seconds" %
(function.func_name, str(t1-t0))
)
return result
return function_timer

这时,你已经在你想测量的函数之前添加了装饰器,像:

@fn_timer
def myfunction(...):
...

例如,让我们测量下排序一个 2000000 个随机数的数组会花费多长时间:

import random

@fn_timer
def random_sort(n):
return sorted([random.random() for i in xrange(n)])

if __name__ == '__main__':
random_sort(2000000)

Total time running random_sort: 1.86385393143 seconds

2、使用timeit模块

另外一个选项是使用 timeit 模块,它给你测量一个平均时间。

为了运行它,在你的终端执行以下命令:

$ python -m timeit -n 4 -r 5 -s "import timing_functions" "timing_functions.random_sort(2000000)"

timing_functions 是你脚本的名字。

在输出的最后,你会看到一些像这样的东西:

4 loops, best of 5: 2.56 sec per loop

表明了运行这个测试 4 次(-n 4),并在每个测试中重复平均 5 次(-r 5),最佳的结果是 2.56 秒。

如果你没有指定测试或者重复,它默认是 10 次循环和 5 次重复。

在Notebook中直接执行下面的语句也可以:

timeit sorted([random.random() for i in xrange(2000000)])

1 loop, best of 3: 2.91 s per loop

3、使用Unix的time命令

尽管如此,装饰器和 timeit 模块都是基于 Python 的。这就是为什么 unix time 工具或许有用,因为它是一个外部的 Python 测量。

为了运行 time 工具类型:

$ time -p python timing_functions.py

将给出如下输出:

Total time running random_sort: 1.70904302597 seconds
real 1.83
user 1.76
sys 0.06

第一行来自于我们定义的装饰器,其他三行是:

real表明了执行脚本花费的总时间

User表明了执行脚本花费在CPU的时间

Sys表明了执行脚本花费在内核函数的时间

因此,real time 和user+sys相加的不同或许表明了时间花费在等待I/O或者系统在忙于执行其他任务。

4、使用cProfile模块

如果你想知道花费在每个函数和方法上的时间,以及它们被调用了多少次,你可以使用CProfile模块。

$ python -m cProfile -s cumulative timing_functions.py

现在你将看到你的代码中每个函数被调用多少次的详细描述,并且它将通过累积花费在每个函数上面的时间来排序(感谢 - s cumulative选项)

Total time running random_sort: 1.99578094482 seconds
2000067 function calls in 2.069 seconds

Ordered by: cumulative time

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
1    0.068    0.068    2.069    2.069 timing_functions.py:1(<module>)
1    0.000    0.000    1.996    1.996 timing_functions.py:7(function_timer)
1    0.469    0.469    1.996    1.996 timing_functions.py:19(random_sort)
1    1.371    1.371    1.371    1.371 {sorted}
2000000    0.156    0.000    0.156    0.000 {method 'random' of '_random.Random' objects}
1    0.002    0.002    0.005    0.005 random.py:40(<module>)
1    0.002    0.002    0.002    0.002 hashlib.py:56(<module>)
1    0.000    0.000    0.001    0.001 random.py:91(__init__)
1    0.000    0.000    0.001    0.001 random.py:100(seed)
1    0.000    0.000    0.000    0.000 {function seed at 0x10289db18}
1    0.000    0.000    0.000    0.000 {posix.urandom}
1    0.000    0.000    0.000    0.000 {binascii.hexlify}
1    0.000    0.000    0.000    0.000 timing_functions.py:6(fn_timer)
2    0.000    0.000    0.000    0.000 {math.log}
1    0.000    0.000    0.000    0.000 functools.py:17(update_wrapper)
1    0.000    0.000    0.000    0.000 __future__.py:48(<module>)
6    0.000    0.000    0.000    0.000 hashlib.py:100(__get_openssl_constructor)
1    0.000    0.000    0.000    0.000 random.py:72(Random)
11    0.000    0.000    0.000    0.000 {getattr}
1    0.000    0.000    0.000    0.000 {math.exp}
2    0.000    0.000    0.000    0.000 {time.time}
7    0.000    0.000    0.000    0.000 __future__.py:75(__init__)
1    0.000    0.000    0.000    0.000 {method 'union' of 'set' objects}
1    0.000    0.000    0.000    0.000 functools.py:39(wraps)
1    0.000    0.000    0.000    0.000 random.py:655(WichmannHill)
1    0.000    0.000    0.000    0.000 {math.sqrt}
1    0.000    0.000    0.000    0.000 random.py:805(SystemRandom)
1    0.000    0.000    0.000    0.000 {_hashlib.openssl_md5}
1    0.000    0.000    0.000    0.000 {_hashlib.openssl_sha256}
1    0.000    0.000    0.000    0.000 {method 'update' of 'dict' objects}
1    0.000    0.000    0.000    0.000 {_hashlib.openssl_sha384}
1    0.000    0.000    0.000    0.000 __future__.py:74(_Feature)
3    0.000    0.000    0.000    0.000 {setattr}
6    0.000    0.000    0.000    0.000 {globals}
1    0.000    0.000    0.000    0.000 {_hashlib.openssl_sha512}
1    0.000    0.000    0.000    0.000 {_hashlib.openssl_sha224}
1    0.000    0.000    0.000    0.000 {_hashlib.openssl_sha1}
1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

你将看到花费在运行你的脚本的总时间是比以前高的。这是我们测量每个函数执行时间的损失。

5、使用line_profile模块

line_profile给出了在你代码每一行花费的CPU时间。

这个模块首先应该被安装,使用命令:

$ pip install line_profiler

下一步,你需要指定你想使用装饰器 @profile 评估哪个函数(你不需要把它 import 到你的文件中)。

@profile
def random_sort2(n):
l = [random.random() for i in range(n)]
l.sort()
return l

if __name__ == "__main__":
random_sort2(2000000)

最后,你可以通过以下命令取得 random_sort2 函数逐行的描述:

$ kernprof -l -v timing_functions.py

-l 标识表明了逐行和 -v 标识表明详细输出。使用这个方法,我们看到了数组结果花费了38%的计算时间,sort()方法花费了剩余的62%的时间。

Wrote profile results to timing_functions.py.lprof
Timer unit: 1e-06 s

Total time: 2.10674 s
File: timing_functions.py
Function: random_sort2 at line 5

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
5                                           @profile
6                                           def random_sort2(n):
7   2000001       802709      0.4     38.1      l = [random.random() for i in xrange(n)]
8         1      1304023 1304023.0     61.9      l.sort()
9         1            3      3.0      0.0      return l

你也将看到,由于时间测量,这个脚本执行花费的或许更长。

6、使用memory_profile模块

memory_profile模块被用于在逐行的基础上,测量你代码内存使用率。尽管如此,它可能使得你的代码运行的更慢。

安装:

$ pip install memory_profiler

也建议安装psutil包,使得 memory_profile 模块运行的更快:

$ pip install psutil

类似 line_profile 的方式,使用装饰器 @profile 来标记哪个函数被跟踪。下一步,键入:

$ python -m memory_profiler timing_functions.py

是的,前面的脚本比之前的 1 或 2 秒需要更长的时间。并且,如果你不安装 psutil 模块,你将一直等待结果。

bogon:~ dfsj$ python -m memory_profiler timing_functions.pyFilename: timing_functions.py

Line # Mem usage Increment Line Contents
================================================
5 31.035 MiB 0.000 MiB @profile
6 def random_sort2(n):
7 80.387 MiB 49.352 MiB l = [random.random() for i in xrange(n)]
8 108.691 MiB 28.305 MiB l.sort()
9 108.691 MiB 0.000 MiB return l


看上面的输出,注意内存使用率的单位是 MiB,这代表的是兆字节(1MiB = 1.05MB)

7、使用guppy包

最后,使用这个包,你可以跟踪每个类型在你代码中每个阶段(字符,元组,字典 等等)有多少对象被创建了。

安装:

$ pip install guppy

下一步,像这样添加到你的代码中:

from guppy import hpy

def random_sort3(n):
hp = hpy()
print "Heap at the beginning of the function\n", hp.heap()
l = [random.random() for i in range(n)]
l.sort()
print "Heap at the end of the function\n", hp.heap()
return l

if __name__ == "__main__":
random_sort3(2000000)

并且这样运行你的代码

$ python timing_functions.py

你将看到一些像下面的输出:

bogon:~ dfsj$ python timing_functions.pyHeap at the beginning of the function
Partition of a set of 26839 objects. Total size = 3419208 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 11070 41 882832 26 882832 26 str
1 6304 23 516240 15 1399072 41 tuple
2 325 1 281848 8 1680920 49 dict (no owner)
3 76 0 237856 7 1918776 56 dict of module
4 1699 6 217472 6 2136248 62 types.CodeType
5 206 1 217424 6 2353672 69 dict of type
6 1660 6 199200 6 2552872 75 function
7 206 1 183304 5 2736176 80 type
8 125 0 136376 4 2872552 84 dict of class
9 1046 4 83680 2 2956232 86 __builtin__.wrapper_descriptor
<95 more rows. Type e.g. '_.more' to view.>
Heap at the end of the function
Partition of a set of 2026849 objects. Total size = 68198720 bytes.
Index Count % Size % Cumulative % Kind (class / dict of class)
0 2000081 99 48001944 70 48001944 70 float
1 183 0 16803960 25 64805904 95 list
2 11072 1 882960 1 65688864 96 str
3 6303 0 516176 1 66205040 97 tuple
4 331 0 283528 0 66488568 97 dict (no owner)
5 76 0 237856 0 66726424 98 dict of module
6 1699 0 217472 0 66943896 98 types.CodeType
7 206 0 217424 0 67161320 98 dict of type
8 1659 0 199080 0 67360400 99 function
9 206 0 183304 0 67543704 99 type
<95 more rows. Type e.g. '_.more' to view.>

通过配置 heap 在你的代码的不同地方,你可以在脚本中学到对象的创建和销毁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python 代码优化