高级调试工具

如果你到达这里,你想潜入,或使用,更先进的工具。对于第一次贡献者和大多数日常开发来说,这通常不是必需的。它们很少被使用,例如在一个新的NumPy发行版附近,或者在一个大的或者特别复杂的变更发生时。

由于并非所有这些工具都是在常规基础上使用的,并且仅在某些系统上可用,请期待差异、问题或怪癖;如果您遇到困难,我们将很乐意为您提供帮助,并感谢您对这些工作流的任何改进或建议。

使用附加工具查找C错误

大多数开发只需要一个典型的调试工具链,如中所示 Debugging . 但举例来说,内存泄漏可能特别微妙或难以缩小。

我们并不期望这些工具中的任何一个能被大多数贡献者运行。但是,您可以确保我们可以更轻松地追踪这些问题:

  • 测试应该覆盖所有代码路径,包括错误路径。

  • 试着写简短的测试。如果您有一个非常复杂的测试,也可以考虑创建一个更简单的测试。这可能很有帮助,因为通常只容易找到哪个测试触发了问题,而不容易找到测试的哪一行。

  • 从不使用 np.empty 如果数据被读取/使用。 valgrind 将注意到这一点并报告错误。当你不在乎随机值的时候,你可以不在乎。

这将有助于我们在您的更改被发布之前抓住任何疏忽,意味着您不必担心出现引用计数错误,这可能会令人生畏。

查找内存泄漏的Python调试版本

Python的调试版本很容易获得,例如 debian 系统,并且可以在所有平台上使用。运行测试或终端通常很容易:

python3.8d runtests.py
# or
python3.8d runtests.py --ipython

已经在书中提到了 Debugging .

Python调试版本将有助于:

  • 找出可能导致随机行为的错误。一个例子是当一个对象被删除后仍然被使用。

  • Python调试构建允许检查正确的引用计数。此操作使用以下附加命令:

    sys.gettotalrefcount()
    sys.getallocatedblocks()
    

与…一起使用 pytest

仅使用调试python构建运行测试套件本身不会发现很多错误。Python调试构建的另一个优点是它允许检测内存泄漏。

使这更容易的一个工具是 pytest-leaks ,可以使用 pip . 不幸的是, pytest 本身可能会泄漏内存,但通常(当前)可以通过删除以下内容来获得良好的效果:

@pytest.fixture(autouse=True)
def add_np(doctest_namespace):
    doctest_namespace['np'] = numpy

@pytest.fixture(autouse=True)
def env_setup(monkeypatch):
    monkeypatch.setenv('PYTHONHASHSEED', '0')

numpy/conftest.py (这可能会随着新的 pytest-leaks 版本或 pytest 更新)。

这样可以方便地运行测试套件或其一部分:

python3.8d runtests.py -t numpy/core/tests/test_multiarray.py -- -R2:3 -s

在哪里? -R2:3pytest-leaks 命令(请参阅其文档) -s 导致输出打印,可能是必要的(在某些版本中,捕获的输出被检测为泄漏)。

请注意,有些测试已知(甚至设计)会泄漏引用,我们尝试对它们进行标记,但会出现一些误报。

valgrind

Valgrind是发现某些内存访问问题的强大工具,应该在复杂的C代码上运行。基本用途 valgrind 通常不需要超过:

PYTHONMALLOC=malloc python runtests.py

在哪里? PYTHONMALLOC=malloc 是避免python本身误报的必要条件。根据系统和valgrind版本,您可能会看到更多误报。 valgrind 支持“suppressions”来忽略其中的一些,并且Python有一个suppression文件(甚至还有一个编译时选项),如果您觉得有必要的话,它可能会有所帮助。

Valgrind帮助:

  • 查找未初始化变量/内存的使用。

  • 检测内存访问冲突(在分配的内存之外读取或写入)。

  • 发现 many 内存泄漏。注意,对于 most 泄漏python调试构建方法(和 pytest-leaks )更敏感。原因是 valgrind 只能检测内存是否丢失。如果:

    dtype = np.dtype(np.int64)
    arr.astype(dtype=dtype)
    

    有不正确的引用计数 dtype ,这是一个bug,但是valgrind看不到它,因为 np.dtype(np.int64) 总是返回相同的对象。但是,并不是所有的数据类型都是单例的,因此这可能会泄漏不同输入的内存。在极少数情况下,NumPy使用 malloc 而不是对Python调试构建不可见的Python内存分配器。 malloc 通常应避免,但也有一些例外(例如 PyArray_Dims 结构是公共API,不能使用Python分配器。)

尽管使用valgrind进行内存泄漏检测比较慢,而且不太敏感,但它还是很方便的:您可以使用valgrind运行大多数程序,而无需修改。

注意事项:

  • Valgrind不支持numpy longdouble ,这意味着测试将失败或被标记为完全正确的错误。

  • 运行NumPy代码前后可能会出现一些错误。

  • 缓存可能意味着错误(特别是内存泄漏)可能无法检测到,或者只能在以后不相关的时间检测到。

valgrind的一大优点是,除了valgrind本身之外,它没有任何需求(尽管您可能希望使用调试构建来实现更好的跟踪)。

与…一起使用 pytest

您可以使用valgrind运行测试套件,当您只对几个测试感兴趣时,这就足够了:

PYTHOMMALLOC=malloc valgrind python runtests.py \
 -t numpy/core/tests/test_multiarray.py -- --continue-on-collection-errors

注意 --continue-on-collection-errors ,由于缺少 longdouble 支持导致失败(如果不运行完整的测试套件,通常不需要这样做)。

如果您希望检测内存泄漏,您还需要 --show-leak-kinds=definite 可能还有更多的选择。就像对 pytest-leaks 已知某些测试会导致valgrind中的错误,可能会也可能不会被标记为错误。

我们开发了 pytest-valgrind 其中:

  • 分别报告每个测试的错误

  • 将内存泄漏缩小到单个测试(默认情况下,valgrind只在程序停止后检查内存泄漏,这非常麻烦)。

请参考其 README 有关详细信息(它包括一个NumPy命令示例)。