Python代码中的捕捉性能-内存分析
在这篇文章中,我将介绍一些可以帮助我们解决Python中痛苦问题的工具,特别是在使用PyPy:内存消耗时。
为什么我们首先关心这一点?为什么我们不关心表演?这些问题的答案相当复杂,但是我会总结一下。
PyPy是替代Python解释器,在那个拥有很大的优势CPython的:速度(通过它的即时编译器),兼容性(这是在更换CPython的几乎下降)和并发性(使用无堆叠和greenlets)。
PyPy的一个缺点是它通常使用比CPython更多的内存,因为它是JIT和垃圾收集器的实现。尽管如此,在某些情况下,它可以比CPython使用更少的内存。
接下来我们将看到如何测量应用程序使用的内存量。
一旦帖子发布,下面的链接将会生效:
- 建立
- 内存分析
- CPU分析 - Python脚本
- CPU分析 - Python解释器
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/
- 上一篇: ftell
- 下一篇: 简单理解python下的变量和内存