【问题标题】:How to debug GC in PyPy?如何在 PyPy 中调试 GC?
【发布时间】:2021-02-28 16:09:36
【问题描述】:

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

例如,在 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 中运行良好,但在 PyPy 中返回以下错误:

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

确实,print(dir(gc)),它为 GC 类返回不同的属性和方法列表,而不是为 PyPy 列出 gc.set_debug()

# 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) 和其他地方(如 herehere),我无法像在 CPython 中那样在 PyPy 中找到一种方法来仔细观察垃圾收集过程。那么,有人可以向我解释一下 PyPy 和 CPython 的 GC 之间的差异如何影响上述测试代码,更准确地说,如何在使用 PyPy 收集之前查看 gc.garbage 中的待处理对象?

我正在运行 Python 3.6.9 和 PyPy 7.3.2。 GCC 对于 CPython 是 8.4.0,对于 PyPy 是 7.3.1。

【问题讨论】:

    标签: python garbage-collection cpython pypy


    【解决方案1】:

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

    不,没有办法检查 所有 正在死亡的对象。事实上,PyPy 的 GC 针对年轻时死去的对象进行了优化,对于所有这些(通常是程序中 所有 对象的 80%-90%),GC 的结构是这样的甚至无法知道什么是垂死的物体。这 80%-90% 的对象占用的空间是批量回收的,而不是一个一个地回收。

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

    【讨论】:

    • 重读,我看到你已经建议faulthandler!对于这种情况,它当然似乎鲜为人知并且非常有帮助。 This post and the comments 可能对@Umlin 有帮助,即使问题本身有点令人震惊..
    • @Armin 我明白了,谢谢你的澄清。如果大多数对象在有机会目视检查它们之前被释放,那么进一步研究 GC 并没有多大意义。还要感谢有关faulthandler 的提示,可能会帮助我调试段错误。如果我没有取得更多进展,我可能会为此提出另一个问题。
    【解决方案2】:

    尝试使用faulthandler 运行python,正如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 modulestrace program(仅限 Linux)

    注意,这些会产生大量的输出

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

    【讨论】:

    • 感谢您提出这些命令,它们可能会帮助我调试段错误。但我的问题是关于如何使用 PyPy 的 GC 进行调试,例如监控可能导致段错误的对象。 Tracebacks 对调试很有用,但它们与 GC 无关。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多