NEP 15 — 合并 multiarray 和 umath#
- 作者:
Nathaniel J. Smith <njs@pobox.com>
- 状态:
最终
- 类型:
标准轨道
- 创建时间:
2018-02-22
- 解决时间:
https://mail.python.org/pipermail/numpy-discussion/2018-June/078345.html
摘要#
让我们将 numpy.core.multiarray 和 numpy.core.umath 合并到一个扩展模块中,并弃用 np.set_numeric_ops。
背景#
目前,numpy 的核心 C 代码被分成两个独立的扩展模块。
numpy.core.multiarray 是由 numpy/core/src/multiarray/*.c 构建的,包含了核心数组功能(特别是 ndarray 对象)。
numpy.core.umath 是由 numpy/core/src/umath/*.c 构建的,包含了 ufunc 机制。
这两个模块各自暴露了它们自己的 C API,分别通过 import_multiarray() 和 import_umath() 访问。其理念是它们应该是独立的模块,multiarray 作为底层,umath 建立在其之上。实际上,这被证明是有问题的。
首先,分层并不完美:当你写 ndarray + ndarray 时,这会调用 ndarray.__add__,然后该方法会调用 ufunc np.add。这意味着 ndarray 需要了解 ufuncs——因此,而不是一个清晰的分层,我们有一个循环依赖。为了解决这个问题,multiarray 导出一个有点可怕的函数,叫做 set_numeric_ops。每次你 import numpy 时的引导过程是:
multiarray及其ndarray对象被加载,但 ndarray 上的算术运算是无效的。umath被加载。set_numeric_ops被用来 monkeypatchumath中的对象到ndarray.__add__等方法上。
此外,set_numeric_ops 被公开为公共 API np.set_numeric_ops。
此外,即使这种分层有效,它最终也会扭曲我们的公共 ABI 的形状。近年来,向 multiarray 的“公共”ABI 添加新函数的常见原因不是因为它们真的需要是公共的,也不是我们期望其他项目使用它们,而仅仅是因为我们需要从 umath 调用它们。这是非常不幸的,因为它使得我们的公共 ABI 不必要地庞大,而且由于我们永远不能从中删除东西,这就造成了持续的维护负担。 C 的工作方式是,你可以有一个对同一扩展模块内的所有内容都可见的内部 API,或者你可以有一个每个人都可以使用的公共 API;你不能(轻易地)拥有一个对 numpy 内部多个扩展模块可见,但不对外部用户可见的 API。
我们还越来越多地将实用代码放入 numpy/core/src/private/,现在该目录包含许多文件,它们被 #include 两次,一次包含到 multiarray 中,一次包含到 umath 中。这非常糟糕,而且完全是为了弥补它们是单独的 C 扩展。npymath 库也被包含在两个扩展模块中。
建议的更改#
此 NEP 提出了三项更改:
我们应该开始将
numpy/core/src/multiarray/*.c和numpy/core/src/umath/*.c一起构建到一个单一的扩展模块中。我们应该使用新的、私有的 API 来设置
ndarray.__add__等,而不是set_numeric_ops。我们应该弃用并最终删除
np.set_numeric_ops。
未建议的更改#
我们不一定建议在我们的源代码组织方面抛弃 multiarray/ 和 umath/ 之间的区别:内部组织是有用的!我们只是想将它们构建到一个单一的扩展模块中。当然,这为潜在的未来重构敞开了大门,届时我们可以根据其优点来评估它们。
它也不建议我们破坏公共 C ABI。我们应该继续提供 import_multiarray() 和 import_umath() 函数——只是现在两个 ABI 都将最终从同一个 C 库加载。由于 import_multiarray() 和 import_umath() 的编写方式,我们仍然需要有 numpy.core.multiarray 和 numpy.core.umath 模块,并且它们需要继续导出 _ARRAY_API 和 _UFUNC_API 对象——但我们可以将一个或两个模块变成非常小的包装器,它们只是从实际定义它们的地方重新导出魔法 API 对象。(有关这些导入如何工作的详细信息,请参阅 numpy/core/code_generators/generate_{numpy,ufunc}_api.py。)
向后兼容性#
唯一兼容性中断的是弃用 np.set_numeric_ops。
否决的替代方案#
为 monkeypatching 保留 set_numeric_ops#
在讨论此 NEP 时,提出了 set_numeric_ops 的一个附加用例:如果您有一个优化的向量数学库(例如 Intel 的 MKL VML、Sleef 或 Yeppp),那么 set_numeric_ops 可以用来 monkeypatch numpy 以使用这些操作而不是 numpy 的内置向量操作。但是,即使我们承认这是一个好主意,使用 set_numeric_ops 并不是最佳的实现方式。所有 set_numeric_ops 允许您做的就是接管 ndarray 上的 Python 语法运算符(+、* 等);它不允许您影响通过其他 API(例如 np.add)调用的操作,或者没有内置语法的操作(例如 np.exp)。此外,您必须重新实现整个 ufunc 机制,而不是仅仅核心循环。另一方面,PyUFunc_ReplaceLoopBySignature API——它在 2006 年添加——允许替换任意 ufunc 的内部循环。这既更简单也更强大——例如,替换 np.add 的内部循环意味着您的代码将自动用于 ndarray + ndarray 以及直接调用 np.add。因此,这似乎不是不弃用 set_numeric_ops 的好理由。
讨论#
版权#
本文档已置于公共领域。