高级调试工具#

如果您到达这里,您想深入了解或使用更高级的工具。对于首次贡献者和大多数日常开发来说,这通常是不必要的。这些很少使用,例如接近新的 NumPy 版本,或者进行大型或特定复杂的更改时。

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

使用附加工具查找 C 错误#

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

我们预计大多数贡献者都不会运行这些工具。但是,您可以确保我们可以更轻松地追踪此类问题:

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

  • 尝试编写简短的测试。如果您有一个非常复杂的测试,请考虑创建一个额外的更简单的测试。这可能会很有帮助,因为通常只能轻松找到哪个测试触发了问题,而不是测试的哪一行。

  • np.empty如果数据被读取/使用,切勿使用。valgrind会注意到这一点并报告错误。当您不关心值时,您可以生成随机值。

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

Python 调试构建#

Python 的调试版本可以很容易地获得,例如通过 Linux 系统上的系统包管理器,但也可以在其他平台上获得,但格式可能不太方便。如果您无法轻松地从系统包管理器安装 Python 的调试版本,您可以使用pyenv自行构建一个。例如,要安装并全局激活 Python 3.10.8 的调试版本,可以执行以下操作:

pyenv install -g 3.10.8
pyenv global 3.10.8

请注意,从源代码构建 Python,因此您必须确保在构建之前安装 Python 的依赖项,请参阅 pyenv 文档以获取特定于平台的安装说明。您可以使用它来安装调试会话可能需要的 Python 依赖项。如果pypi上没有可用的调试轮,您将需要从源代码构建依赖项,并确保您的依赖项也被编译为调试版本。pyenv installpip

通常,Python 的调试版本会命名为 Python 可执行文件,pythond而不是 python.要检查是否安装了 Python 的调试版本,可以运行 eg来获取 Python 可执行文件的构建配置。将使用(例如)中的调试编译器选项构建调试版本 。pythond -m sysconfigCFLAGS-g -Og

运行 Numpy 测试或交互式终端通常非常简单:

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

并且已经在调试中提到过。

Python 调试构建将有助于:

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

  • Python 调试版本允许检查正确的引用计数。这可以使用附加命令来实现:

    sys.gettotalrefcount()
    sys.getallocatedblocks()
    
  • Python 调试版本允许使用 gdb 和其他 C 调试器更轻松地进行调试。

与#一起使用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:3命令在哪里pytest-leaks(请参阅其文档), -s导致输出打印并且可能是必要的(在某些版本中捕获的输出被检测为泄漏)。

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

valgrind#

Valgrind 是一个强大的工具,可以发现某些内存访问问题,并且应该在复杂的 C 代码上运行。基本使用valgrind通常只需要:

PYTHONMALLOC=malloc valgrind python runtests.py

PYTHONMALLOC=malloc必要避免 python 本身的误报。根据系统和 valgrind 版本,您可能会看到更多误报。 valgrind支持“抑制”来忽略其中一些,并且 Python 确实有一个抑制文件(甚至是编译时选项),如果您认为有必要,它可能会有所帮助。

Valgrind 可以帮助:

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

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

  • 发现很多内存泄漏。请注意,对于大多数泄漏,Python 调试构建方法(和pytest-leaks)要敏感得多。原因是valgrind只能检测内存是否确实丢失。如果:

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

    的引用计数不正确dtype,这是一个错误,但 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 以及可能更多的 valgrind 选项。正如pytest-leaks已知某些测试会泄漏导致 valgrind 中的错误一样,并且可能会也可能不会如此标记。

我们开发了pytest-valgrind,它:

  • 单独报告每个测试的错误

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

请参阅其README了解更多信息(其中包括 NumPy 的示例命令)。