在这篇文章中,我将介绍一些可以帮助我们解决Python中痛苦问题的工具,特别是在使用PyPy:内存消耗时。
为什么我们首先关心这一点?为什么我们不关心表演?这些问题的答案相当复杂,但是我会总结一下。
PyPy是替代Python解释器,在那个拥有很大的优势CPython的:速度(通过它的即时编译器),兼容性(这是在更换CPython的几乎下降)和并发性(使用无堆叠和greenlets)。
PyPy的一个缺点是它通常使用比CPython更多的内存,因为它是JIT和垃圾收集器的实现。尽管如此,在某些情况下,它可以比CPython使用更少的内存。
接下来我们将看到如何测量应用程序使用的内存量。
系列索引
一旦帖子发布,下面的链接将会生效:
诊断内存使用情况
memory_profiler
可以使用一个库来测量解释器用来运行工作负载的内存量memory_profiler
。它通过点可用:
pip install memory_profiler
还安装 psutil
依赖项:
pip install psutil
这个工具的优点是它在一个Python脚本中逐行显示内存消耗。这有助于我们从脚本中找到可以重写的地方。但是这个分析有一个缺点。您的代码将比通常的脚本慢10到20倍。
如何使用它?你只需要添加一个指令@profile()
到你需要测量的函数。
让我们看看它的行动!我们将用我们之前的文章中的primes脚本作为模型,稍作修改以消除统计部分。它可以在GitHub上这里。
from memory_profiler import profile
@profile(precision=6)
def primes(n):
if n == 2:
return [2]
elif n < 2:
return []
s = range(3, n + 1, 2)
mroot = n ** 0.5
half = (n + 1) / 2 - 1
i = 0
m = 3
while m <= mroot:
if s[i]:
j = (m * m - 3) / 2
s[j] = 0
while j < half:
s[j] = 0
j += m
i = i + 1
m = 2 * i + 3
return [2] + [x for x in s if x]
len(primes(100000))
要开始测量,请使用以下命令PyPy:
pypy -m memory_profiler 02.primes-v3.py
或者更短,直接在脚本中导入memory_profiler:
pypy -m memory_profiler 02.primes-v3.py
在执行这条线之后,我们会看到这样的PyPy
Line # Mem usage Increment Line Contents
================================================
54 35.312500 MiB 0.000000 MiB @profile(precision=6)
55 def primes(n):
56 35.351562 MiB 0.039062 MiB if n == 2:
57 return [2]
58 35.355469 MiB 0.003906 MiB elif n < 2:
59 return []
60 35.355469 MiB 0.000000 MiB s = []
61 59.515625 MiB 24.160156 MiB for i in range(3, n+1):
62 59.515625 MiB 0.000000 MiB if i % 2 != 0:
63 59.515625 MiB 0.000000 MiB s.append(i)
64 59.546875 MiB 0.031250 MiB mroot = n ** 0.5
65 59.550781 MiB 0.003906 MiB half = (n + 1) / 2 - 1
66 59.550781 MiB 0.000000 MiB i = 0
67 59.550781 MiB 0.000000 MiB m = 3
68 59.554688 MiB 0.003906 MiB while m <= mroot:
69 59.554688 MiB 0.000000 MiB if s[i]:
70 59.554688 MiB 0.000000 MiB j = (m * m - 3) / 2
71 59.554688 MiB 0.000000 MiB s[j] = 0
72 59.554688 MiB 0.000000 MiB while j < half:
73 59.554688 MiB 0.000000 MiB s[j] = 0
74 59.554688 MiB 0.000000 MiB j += m
75 59.554688 MiB 0.000000 MiB i = i + 1
76 59.554688 MiB 0.000000 MiB m = 2 * i + 3
77 59.554688 MiB 0.000000 MiB l = [2]
78 59.679688 MiB 0.125000 MiB for x in s:
79 59.679688 MiB 0.000000 MiB if x:
80 59.679688 MiB 0.000000 MiB l.append(x)
81 59.683594 MiB 0.003906 MiB return l
我们可以看到这个脚本使用了RAM的24.371094 MiB。我们来分析一下。我们可以看到,构建数组数组时使用了大部分。它排除偶数并保存所有其他数据。
我们可以通过range
调用一个增量参数来改善这一点。在这种情况下,脚本将如下所示:
from memory_profiler import profile
@profile(precision=6)
def primes(n):
if n == 2:
return [2]
elif n < 2:
return []
s = range(3, n + 1, 2)
mroot = n ** 0.5
half = (n + 1) / 2 - 1
i = 0
m = 3
while m <= mroot:
if s[i]:
j = (m * m - 3) / 2
s[j] = 0
while j < half:
s[j] = 0
j += m
i = i + 1
m = 2 * i + 3
l = [2]
for x in s:
if x:
l.append(x)
return l
len(primes(100000))
如果我们再次测量,我们看到以下内容:
Line # Mem usage Increment Line Contents
================================================
27 35.343750 MiB 0.000000 MiB @profile(precision=6)
28 def primes(n):
29 35.382812 MiB 0.039062 MiB if n == 2:
30 return [2]
31 35.382812 MiB 0.000000 MiB elif n < 2:
32 return []
33 35.386719 MiB 0.003906 MiB s = range(3, n + 1, 2)
34 35.417969 MiB 0.031250 MiB mroot = n ** 0.5
35 35.417969 MiB 0.000000 MiB half = (n + 1) / 2 - 1
36 35.417969 MiB 0.000000 MiB i = 0
37 35.421875 MiB 0.003906 MiB m = 3
38 58.019531 MiB 22.597656 MiB while m <= mroot:
39 58.019531 MiB 0.000000 MiB if s[i]:
40 58.019531 MiB 0.000000 MiB j = (m * m - 3) / 2
41 58.019531 MiB 0.000000 MiB s[j] = 0
42 58.019531 MiB 0.000000 MiB while j < half:
43 58.019531 MiB 0.000000 MiB s[j] = 0
44 58.019531 MiB 0.000000 MiB j += m
45 58.019531 MiB 0.000000 MiB i = i + 1
46 58.019531 MiB 0.000000 MiB m = 2 * i + 3
47 58.019531 MiB 0.000000 MiB l = [2]
48 58.089844 MiB 0.070312 MiB for x in s:
49 58.089844 MiB 0.000000 MiB if x:
50 58.089844 MiB 0.000000 MiB l.append(x)
51 58.093750 MiB 0.003906 MiB return l
太好了,现在我们的内存消耗降到了22.75 MiB。通过使用列表理解也可以改进一点。
from memory_profiler import profile
@profile(precision=6)
def primes(n):
if n == 2:
return [2]
elif n < 2:
return []
s = range(3, n + 1, 2)
mroot = n ** 0.5
half = (n + 1) / 2 - 1
i = 0
m = 3
while m <= mroot:
if s[i]:
j = (m * m - 3) / 2
s[j] = 0
while j < half:
s[j] = 0
j += m
i = i + 1
m = 2 * i + 3
return [2] + [x for x in s if x]
len(primes(100000))
再次测量:
Line # Mem usage Increment Line Contents
================================================
4 35.425781 MiB 0.000000 MiB @profile(precision=6)
5 def primes(n):
6 35.464844 MiB 0.039062 MiB if n == 2:
7 return [2]
8 35.464844 MiB 0.000000 MiB elif n < 2:
9 return []
10 35.464844 MiB 0.000000 MiB s = range(3, n + 1, 2)
11 35.500000 MiB 0.035156 MiB mroot = n ** 0.5
12 35.500000 MiB 0.000000 MiB half = (n + 1) / 2 - 1
13 35.500000 MiB 0.000000 MiB i = 0
14 35.500000 MiB 0.000000 MiB m = 3
15 57.683594 MiB 22.183594 MiB while m <= mroot:
16 57.683594 MiB 0.000000 MiB if s[i]:
17 57.683594 MiB 0.000000 MiB j = (m * m - 3) / 2
18 57.683594 MiB 0.000000 MiB s[j] = 0
19 57.683594 MiB 0.000000 MiB while j < half:
20 57.683594 MiB 0.000000 MiB s[j] = 0
21 57.683594 MiB 0.000000 MiB j += m
22 57.683594 MiB 0.000000 MiB i = i + 1
23 57.683594 MiB 0.000000 MiB m = 2 * i + 3
24 57.847656 MiB 0.164062 MiB return [2] + [x for x in s if x]
我们的脚本的最终版本只消耗22.421875 MiB。与第一版相比,差不多减少了10%。
作者:Alecsandru Patrascu,alecsandru.patrascu [at] rinftech [dot] com
原文:https://pythonfiles.wordpress.com/2017/05/18/hunting-python-performance-part-2/