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.multiarraynumpy.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 時的引導程序是

  1. multiarray 及其 ndarray 物件被載入,但 ndarray 上的算術運算會損壞。

  2. umath 被載入。

  3. set_numeric_ops 用於 monkeypatch 所有類似 ndarray.__add__ 的方法,使其使用來自 umath 的物件。

此外,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 提議三項變更

  1. 我們應該開始將 numpy/core/src/multiarray/*.cnumpy/core/src/umath/*.c 一起建置到單一擴充模組中。

  2. 我們應該使用一些新的私有 API 來設定 ndarray.__add__ 和相關功能,而不是使用 set_numeric_ops

  3. 我們應該棄用,並最終移除 np.set_numeric_ops

未提議的變更#

我們不一定提議要丟棄 multiarray/ 和 umath/ 在原始碼組織方面的區別:內部組織是有用的!我們只是想將它們一起建置到單一擴充模組中。當然,這確實為未來可能的重構打開了大門,然後我們可以根據它們出現時的優點來評估它們。

這也不表示我們將破壞公共 C ABI。我們應該繼續提供 import_multiarray()import_umath() 函式 – 只是現在這兩個 ABI 最終將從同一個 C 程式庫載入。由於 import_multiarray()import_umath() 的撰寫方式,我們仍然需要有稱為 numpy.core.multiarraynumpy.core.umath 的模組,而且它們需要繼續匯出 _ARRAY_API_UFUNC_API 物件 – 但我們可以將其中一個或兩個模組變成微小的墊片,它們只是從實際定義它們的任何位置重新匯出神奇的 API 物件。(請參閱 numpy/core/code_generators/generate_{numpy,ufunc}_api.py 以了解這些匯入如何運作的詳細資訊。)

向後相容性#

唯一的相容性中斷是棄用 np.set_numeric_ops

拒絕的替代方案#

保留 set_numeric_ops 以進行 monkeypatching#

在討論此 NEP 時,針對 set_numeric_ops 提出了一個額外的用例:如果你有一個優化的向量數學程式庫(例如 Intel 的 MKL VML、Sleef 或 Yeppp),則可以使用 set_numeric_ops 來 monkeypatch numpy,以使用這些運算而不是 numpy 的內建向量運算。但是,即使我們承認這是一個很棒的想法,使用 set_numeric_ops 並不是實際上的最佳方法。所有 set_numeric_ops 允許你做的是接管 Python 在 ndarray 上的語法運算子(+* 等);它不讓你影響透過其他 API 呼叫的運算(例如 np.add),或沒有內建語法的運算(例如 np.exp)。此外,你必須重新實作整個 ufunc 機制,而不僅僅是核心迴圈。另一方面,PyUFunc_ReplaceLoopBySignature API(於 2006 年新增)允許替換任意 ufunc 的內部迴圈。這既更簡單又更強大 – 例如,替換 np.add 的內部迴圈意味著你的程式碼將自動用於 ndarray + ndarray 以及直接呼叫 np.add。因此,這似乎不是不棄用 set_numeric_ops 的好理由。

討論#