如何在 PyPy 中调试 GC?

How to debug GC in PyPy?

我最近一直在尝试从 CPython 切换到 PyPy,并且在尝试解决一个错误时,更准确地说是一个带有 SIGSEGV 信号的错误 139(所以是分段错误),我试图调查通过查看 gc.garbage 属性列表通过 GC 模块进行垃圾收集。

在 CPython 中,例如,我可以 运行 下面的一段代码(取自 there 并进行了修改)来检查 GC 垃圾列表中的延迟对象:

import gc

gc.set_debug(gc.DEBUG_SAVEALL)

print(gc.get_count())
lst = []
lst.append(lst)
list_id = id(lst)
del lst
gc.collect()
for item in gc.garbage:
    print(item) if list_id == id(item) else "pass"

此代码在 CPython 中运行良好,但 returns 在 PyPy 中出现以下错误:

AttributeError: module 'gc' has no attribute 'set_debug'

的确,print(dir(gc)),returns 不同的 GC class 属性和方法列表,没有列出 gc.set_debug() PyPy :

# Under CPython
['DEBUG_COLLECTABLE', 'DEBUG_LEAK', 'DEBUG_SAVEALL', 'DEBUG_STATS', 'DEBUG_UNCOLLECTABLE', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'callbacks', 'collect', 'disable', 'enable', 'garbage', 'get_count', 'get_debug', 'get_objects', 'get_referents', 'get_referrers', 'get_stats', 'get_threshold', 'is_tracked', 'isenabled', 'set_debug', 'set_threshold']

# Under PyPy
['GcCollectStepStats', 'GcRef', '__doc__', '__loader__', '__name__', '__package__', '__spec__', '_dump_rpy_heap', '_get_stats', 'collect', 'collect_step', 'disable', 'disable_finalizers', 'dump_rpy_heap', 'enable', 'enable_finalizers', 'garbage', 'get_objects', 'get_referents', 'get_referrers', 'get_rpy_memory_usage', 'get_rpy_referents', 'get_rpy_roots', 'get_rpy_type_index', 'get_stats', 'get_typeids_list', 'get_typeids_z', 'hooks', 'isenabled']

如果我理解正确,设置 gc.set_debug(gc.DEBUG_SAVEALL) 会将无法访问的对象保留在 GC 的垃圾列表中,因此如果没有它,gc.collect() 将尝试释放对象的内存分配。但我之前想检查垃圾列表,因为我怀疑它触发了我试图跟踪的分段错误。

尽管查看了 PyPy 关于垃圾收集的文档(如 herehere) and other places (like here or here),我一直无法找到一种方法来在 PyPy 中仔细观察垃圾收集过程,就像在 CPython 中可以做到的那样。那么,有人可以向我解释 PyPy 和 CPython 的 GC 之间的差异如何影响上述测试代码,更准确地说,如何在使用 PyPy 收集之前观察 gc.garbage 中的未决对象?

我 运行宁 Python 3.6.9 与 PyPy 7.3.2。 GCC 对于 CPython 是 8.4.0,对于 PyPy 是 7.3.1。

你想做的事情是不可能的。即使在 CPython 上,列表 gc.garbage 也不会包含所有被回收的对象,即使您启用调试模式,但只包含那些被发现处于循环中的对象。除了循环查找逻辑本身的作者之外,这不太可能与任何人相关。在 PyPy 上,“处于循环中”的概念甚至更不相关;正如您可能已经从您指向的各种链接中了解到的那样,PyPy 的 GC 是完全不同的。

不,无法检查所有 正在消亡的对象。事实上,PyPy 的 GC 针对早逝的对象进行了优化,对于所有这些(通常是程序中 all 对象的 80%-90%),GC 的结构是这样一来,甚至 都无法知道 垂死的物体是什么。这 80%-90% 的对象占据 space 是批量回收的,不是一个一个回收的。

您很有可能从错误的角度看待问题。如果您能详细描述一下您的问题是什么,我们可以尝试提出更好的解决方案。同时,请注意,当您遇到段错误时,您可以 运行 pypy -X faulthandler 至少获得某种回溯。

尝试 运行 python 和 faulthandler as suggested by this answer to python tracing a segmentation fault

这应该适用于 CPython 和 PyPy

% python3 -q -X faulthandler -c "import ctypes; ctypes.string_at(0)"
Fatal Python error: Segmentation fault

Current thread 0x00007fe10d301740 (most recent call first):
  File "/usr/lib/python3.8/ctypes/__init__.py", line 514 in string_at
  File "<string>", line 1 in <module>
Segmentation fault (core dumped)

当您遇到段错误时,一些更高级的调试也可能会有所帮助。您可以跟随trace module or the strace program(仅限Linux)

当心,这些会产生巨大的输出量

python -m trace --trace myprogram.py
strace python myprogram.py