Python代码中的捕捉性能-内存分析

在这篇文章中,我将介绍一些可以帮助我们解决Python中痛苦问题的工具,特别是在使用PyPy:内存消耗时。

为什么我们首先关心这一点?为什么我们不关心表演?这些问题的答案相当复杂,但是我会总结一下。

PyPy是替代Python解释器,在那个拥有很大的优势CPython的:速度(通过它的即时编译器),兼容性(这是在更换CPython的几乎下降)和并发性(使用无堆叠greenlets)。

PyPy的一个缺点是它通常使用比CPython更多的内存,因为它是JIT和垃圾收集器的实现。尽管如此,在某些情况下,它可以比CPython使用更少的内存。

接下来我们将看到如何测量应用程序使用的内存量。

系列索引

一旦帖子发布,下面的链接将会生效:

  1. 建立
  2. 内存分析
  3. CPU分析 - Python脚本
  4. 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/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值