高级调试工具#
如果您看到这里,说明您想深入了解或使用更高级的工具。对于首次贡献者和大多数日常开发来说,这通常是不必要的。这些工具更少见地使用,例如在 NumPy 新版本发布前后,或进行大型或特别复杂的更改时。
由于并非所有这些工具都经常使用,并且仅在某些系统上可用,因此请预期存在差异、问题或怪癖;如果您遇到困难,我们将乐于提供帮助,并感谢您对这些工作流程的任何改进或建议。
使用其他工具查找 C 错误#
大多数开发只需要如调试中所示的典型调试工具链。但是,例如内存泄漏可能特别细微或难以缩小范围。
我们不期望大多数贡献者运行这些工具中的任何一个。但是,您可以确保我们可以更轻松地追踪此类问题。
测试应该涵盖所有代码路径,包括错误路径。
尝试编写简短而简单的测试。如果您有一个非常复杂的测试,请考虑创建一个额外的更简单的测试。这可能会有所帮助,因为通常很容易找到哪个测试触发了问题,而不是测试的哪一行。
如果读取/使用数据,切勿使用
np.empty
。valgrind
会注意到这一点并报告错误。如果您不关心值,则可以生成随机值。
这将帮助我们在您的更改发布之前发现任何疏忽,这意味着您不必担心出现引用计数错误,这可能会令人望而生畏。
Python 调试版本#
例如,通过 Linux 系统上的系统包管理器可以轻松获得 Python 的调试版本,但其他平台上也可能以不太方便的形式提供。如果您无法从系统包管理器轻松安装 Python 的调试版本,则可以使用pyenv 自己构建一个。例如,要安装并全局激活 Python 3.10.8 的调试版本,可以执行以下操作:
pyenv install -g 3.10.8
pyenv global 3.10.8
请注意,pyenv install
从源代码构建 Python,因此您必须确保在构建之前安装 Python 的依赖项,请参阅 pyenv 文档以获取特定于平台的安装说明。您可以使用pip
安装调试会话可能需要的 Python 依赖项。如果pypi上没有可用的调试轮子,则需要从源代码构建依赖项并确保您的依赖项也作为调试版本编译。
通常,Python 调试版本的 Python 可执行文件名为pythond
而不是python
。要检查是否安装了 Python 的调试版本,您可以运行例如pythond -m sysconfig
以获取 Python 可执行文件的构建配置。调试版本将在CFLAGS
中使用调试编译器选项构建(例如-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)
总是返回相同的对象。但是,并非所有 dtype 都是单例,因此这可能会因不同的输入而导致内存泄漏。在极少数情况下,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 的示例命令)。