高级调试工具#
如果到达此处,则需要深入研究或使用更高级的工具. 对于首次贡献者和大多数日常开发而言,这通常不是必需的. 这些工具的使用频率较低,例如,接近新的 NumPy 版本发布时,或者进行了较大或特别复杂的更改时.
由于并非所有这些工具都定期使用,并且仅在某些系统上可用,因此请注意差异,问题或怪癖; 如果您遇到困难,我们将很乐意提供帮助,并感谢对这些工作流程的任何改进或建议.
使用其他工具查找 C 错误#
大多数开发不需要比 Debugging 中所示的典型调试工具链更多的东西. 但是,例如,内存泄漏可能特别微妙或难以缩小范围.
我们不希望大多数贡献者运行这些工具. 但是,您可以确保我们可以更轻松地追踪此类问题:
测试应涵盖所有代码路径,包括错误路径.
尝试编写简短而简单的测试. 如果您有一个非常复杂的测试,请考虑创建一个额外的更简单的测试. 这可能会有所帮助,因为通常很容易找到哪个测试触发了问题,而不是测试的哪一行.
如果读取/使用数据,则永远不要使用
np.empty.valgrind会注意到这一点并报告错误. 如果您不关心值,则可以改为生成随机值.
这将帮助我们在发布更改之前发现任何疏忽,这意味着您不必担心进行引用计数错误,这可能会让人望而却步.
Python 调试版本#
Python 的调试版本很容易获得,例如通过 Linux 系统上的系统包管理器,但在其他平台上也可以使用,可能格式不太方便.如果您无法从系统包管理器轻松安装 Python 的调试版本,您可以自己使用 pyenv 构建一个.例如,要安装并全局激活 Python 3.13.3 的调试版本,可以这样做:
pyenv install -g 3.13.3
pyenv global 3.13.3
请注意, pyenv install 从源代码构建 Python,因此您必须确保在构建之前安装了 Python 的依赖项,有关平台特定的安装说明,请参阅 pyenv 文档.您可以使用 pip 安装调试会话可能需要的 Python 依赖项.如果在 pypi 上没有可用的调试 wheel,您将需要从源代码构建依赖项,并确保您的依赖项也被编译为调试版本.
通常,Python 的调试版本将 Python 可执行文件命名为 pythond 而不是 python .要检查是否安装了 Python 的调试版本,您可以运行例如 pythond -m sysconfig 来获取 Python 可执行文件的构建配置.调试版本将使用 CFLAGS 中的调试编译器选项(例如 -g -Og )构建.
运行 Numpy 测试或交互式终端通常很容易,只需:
python3.8d runtests.py
# or
python3.8d runtests.py --ipython
并且已经在 Debugging 中提到.
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)总是返回相同的对象.但是,并非所有 dtypes 都是单例,因此这可能会泄漏不同输入的内存.在极少数情况下,NumPy 使用malloc而不是 Python 内存分配器,这对于 Python 调试版本是不可见的.通常应避免使用malloc,但也有一些例外(例如,PyArray_Dims结构是公共 API,不能使用 Python 分配器.)
即使使用 valgrind 进行内存泄漏检测速度较慢且不太敏感,它也可以很方便:您可以在不修改的情况下使用 valgrind 运行大多数程序.
需要注意的事项:
Valgrind 不支持 numpy 的
longdouble,这意味着测试将会失败或者被标记为完全正常的错误.在运行 NumPy 代码之前和之后,预计会出现一些错误.
缓存可能意味着错误(特别是内存泄漏)可能无法被检测到,或者只会在稍后不相关的时间被检测到.
valgrind 的一个很大的优点是它没有除了 valgrind 本身之外的其他要求(虽然您可能想使用调试版本以获得更好的回溯).
与 pytest 一起使用#
您可以使用 valgrind 运行测试套件,如果您只对几个测试感兴趣,这可能就足够了:
PYTHONMALLOC=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 的示例命令).