Pympler摘要似乎没有意义

itf*_*itf 9 python memory-leaks memory-profiling python-3.x

我正在与Pympler进行一些完整性检查,以确保在我尝试分析实际脚本时我理解结果,但我对结果感到有些困惑.以下是我尝试的健全性检查:

SANITY CHECK 1:我启动Python(3)控制台并执行以下操作:

from pympler import summary, muppy
sum = summary.summarize(muppy.get_objects())
summary.print_(sum)
Run Code Online (Sandbox Code Playgroud)

这导致以下总结:

                               types |   # objects |   total size
==================================== | =========== | ============
                         <class 'str |       16047 |      1.71 MB
                        <class 'dict |        2074 |      1.59 MB
                        <class 'type |         678 |    678.27 KB
                        <class 'code |        4789 |    673.68 KB
                         <class 'set |         464 |    211.00 KB
                        <class 'list |        1319 |    147.16 KB
                       <class 'tuple |        1810 |    120.03 KB
                     <class 'weakref |        1269 |     99.14 KB
          <class 'wrapper_descriptor |        1124 |     87.81 KB
  <class 'builtin_function_or_method |         918 |     64.55 KB
                 <class 'abc.ABCMeta |          64 |     62.25 KB
           <class 'method_descriptor |         877 |     61.66 KB
                         <class 'int |        1958 |     58.88 KB
           <class 'getset_descriptor |         696 |     48.94 KB
                 function (__init__) |         306 |     40.64 KB
Run Code Online (Sandbox Code Playgroud)

如果我刚刚启动了一个新的Python会话,那么内存中的所有字符串,字典,列表等都是如何?我不认为Pympler会在所有会话中总结结果(这没有任何意义,但这是我能想到的唯一可能性).

SANITY CHECK 2:由于我不太了解tabula rasa Python会话的摘要结果,所以在定义一些变量/数据结构之后,让我们看一下总结的差异.我启动另一个控制台并执行以下操作:

from pympler import summary, muppy
sum = summary.summarize(muppy.get_objects())
a = {}
b = {}
c = {}
d = {'a': [0, 0, 1, 2], 't': [3, 3, 3, 1]}
sum1 = summary.summarize(muppy.get_objects())
summary.print_(summary.get_diff(sum, sum1))
Run Code Online (Sandbox Code Playgroud)

这导致以下总结:

                         types |   # objects |   total size
============================== | =========== | ============
                  <class 'list |        3247 |    305.05 KB
                   <class 'str |        3234 |    226.04 KB
                   <class 'int |         552 |     15.09 KB
                  <class 'dict |           1 |    480     B
              function (_keys) |           0 |      0     B
           function (get_path) |           0 |      0     B
          function (http_open) |           0 |      0     B
            function (memoize) |           0 |      0     B
                function (see) |           0 |      0     B
           function (recvfrom) |           0 |      0     B
              function (rfind) |           0 |      0     B
      function (wm_focusmodel) |           0 |      0     B
    function (_parse_makefile) |           0 |      0     B
  function (_decode_pax_field) |           0 |      0     B
             function (__gt__) |           0 |      0     B
Run Code Online (Sandbox Code Playgroud)

我以为我刚刚初始化了四个新的词典(虽然3个是空的),为什么Muppy只显示1个新词典对象的区别?此外,为什么有成千上万的新字符串和列表,更不用说整数?

SANITY CHECK 3:再次,我开始一个新的Python会话,但这一次想看看Pympler如何处理更复杂的数据类型,如字典列表.

from pympler import muppy, summary
sum = summary.summarize(muppy.get_objects())
a = [{}, {}, {}, {'a': [0, 0, 1, 2], 't': [3, 3, 3, 1]}, {'a': [1, 2, 3, 4]}]
sum1 = summary.summarize(muppy.get_objects())
summary.print_(summary.get_diff(sum, sum1))
Run Code Online (Sandbox Code Playgroud)

其结果如下:

                                                types |   # objects |   total size
===================================================== | =========== | ============
                                         <class 'list |        3233 |    303.88 KB
                                          <class 'str |        3270 |    230.71 KB
                                          <class 'int |         554 |     15.16 KB
                                         <class 'dict |          10 |      5.53 KB
                                         <class 'code |          16 |      2.25 KB
                                         <class 'type |           2 |      1.98 KB
                                        <class 'tuple |           6 |    512     B
                            <class 'getset_descriptor |           4 |    288     B
                                  function (__init__) |           2 |    272     B
  <class '_frozen_importlib_external.SourceFileLoader |           3 |    168     B
                 <class '_frozen_importlib.ModuleSpec |           3 |    168     B
                                      <class 'weakref |           2 |    160     B
                                  function (__call__) |           1 |    136     B
                                      function (Find) |           1 |    136     B
                                  function (<lambda>) |           1 |    136     B
Run Code Online (Sandbox Code Playgroud)

即使列表和词典嵌套有点令人费解,按照我的计算,我添加了5个新词典和4个新列表.

有人能解释一下Muppy如何计算物体?

Nic*_*lay 2

1get_objects在新的Python会话中

\n

summary.summarize(muppy.get_objects())返回在启动和from pympler import summary, muppy运行期间实例化的任何对象,这解释了大量的计数。

\n

get_objects2 两次调用的区别

\n

2.1. 许多新对象不是我们创建的

\n

请记住,sum生成的对象summary.summarize()是在第一个快照之后创建的,这解释了“数千个新字符串和列表”。您可以通过将测试重写为以下方式来解决此问题:

\n
from pympler import summary, muppy\no1 = muppy.get_objects()\na = {}\nb = {}\nc = {}\nd = {\'a\': [0, 0, 1, 2], \'t\': [3, 3, 3, 1]}\no2 = muppy.get_objects()\nsummary.print_(summary.get_diff(summary.summarize(o1), summary.summarize(o2)))\n
Run Code Online (Sandbox Code Playgroud)\n

o1这将减少和其他几个对象的大列表的无关差异:

\n
>>> for o in diff[\'+\']:\n...     print("%s - %s" % (type(o), o if len(o) < 10 else "long list"))\n...\n<class \'str\'> - o2\n<class \'list\'> - long list\n<class \'dict\'> - {\'a\': [0, 0, 1, 2], \'t\': [3, 3, 3, 1]}\n<class \'list\'> - [\'o2\', \'muppy\', \'get_objects\']\n<class \'list\'> - [0, 0, 1, 2]\n<class \'list\'> - [3, 3, 3, 1]\n
Run Code Online (Sandbox Code Playgroud)\n

2.2. 创建和报告的字典数量不匹配

\n

为了理解这一点,我们需要知道 pympler 到底在检查什么。

\n

muppy.get_objects实施依赖

\n
    \n
  • Python 的gc.get_objects(),它是“收集器跟踪的所有对象的列表”( gc.is_tracked),堆栈帧除外。\n
    \n

    原子类型的实例\xe2\x80\x99 不会被跟踪,非原子类型的实例(容器、用户定义的对象\xe2\x80\xa6)会被跟踪。然而,可以存在一些特定于类型的优化,以抑制简单实例的垃圾收集器足迹(例如仅包含原子键和值的字典

    \n
    \n
  • \n
  • 然后,它添加从步骤 1 获取的对象中引用的对象 ( gc.get_referents ),但不包括“容器对象” -类型中具有Py_TPFLAGS_HAVE_GC 的__flags__对象。(这似乎是一个错误,因为排除所有容器对象会错过容器类型的“简单实例”,这些实例不是 GC 跟踪的。更新 在 2019-11-12 发布的 v0.8 中修复)
  • \n
\n

如果您按照上面的建议存储对象列表o2并使用以下命令检查哪些对象被占用:

\n
def tracked(obj_list, obj):\n    import gc\n    return {"tracked_by_muppy": any(id(item) == id(obj) for item in obj_list),\n            "gc_tracked": gc.is_tracked(obj)}\n
Run Code Online (Sandbox Code Playgroud)\n

你会看到:

\n
    \n
  • 空字典不受 GC 跟踪,并且由于它们仅从局部变量引用,因此 muppy 不会考虑它们:

    \n
      tracked(o2, a)  # => {\'tracked_by_muppy\': False, \'gc_tracked\': False}\n
    Run Code Online (Sandbox Code Playgroud)\n
  • \n
  • 这个不平凡的字典d GC 跟踪的,因此出现在 muppy 报告中:

    \n
      tracked(o2, d)  # => {\'tracked_by_muppy\': True, \'gc_tracked\': True}\n
    Run Code Online (Sandbox Code Playgroud)\n
  • \n
\n