# 1.6。創建 Numpy 通用函數
> 原文: [http://numba.pydata.org/numba-doc/latest/user/vectorize.html](http://numba.pydata.org/numba-doc/latest/user/vectorize.html)
## 1.6.1。 `@vectorize`裝飾器
Numba 的 vectorize 允許 Python 函數將標量輸入參數用作 NumPy [ufuncs](http://docs.scipy.org/doc/numpy/reference/ufuncs.html) 。創建傳統的 NumPy ufunc 并不是最直接的過程,而是涉及編寫一些 C 代碼。 Numba 讓這很容易。使用 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 裝飾器,Numba 可以將純 Python 函數編譯為一個 ufunc,它在 NumPy 陣列上運行的速度與用 C 編寫的傳統 ufunc 一樣快。
使用 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") ,可以將函數編寫為通過輸入標量而不是數組進行操作。 Numba 將生成周圍循環(或 _ 內核 _),允許對實際輸入進行有效迭代。
[`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 裝飾器有兩種操作模式:
* 渴望或裝飾時間,編譯:如果您將一個或多個類型簽名傳遞給裝飾器,您將構建 Numpy 通用函數(ufunc)。本小節的其余部分描述了使用裝飾時編譯構建 ufunc。
* 懶惰或調用時編譯:當沒有給出任何簽名時,裝飾器會給你一個 Numba 動態通用函數( [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") ),它在使用以前不支持的輸入類型調用時動態編譯新內核。后面的小節“[動態通用函數](#dynamic-universal-functions)”更深入地描述了這種模式。
如上所述,如果您將簽名列表傳遞給 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 裝飾器,您的函數將被編譯為 Numpy ufunc。在基本情況下,只會傳遞一個簽名:
```py
from numba import vectorize, float64
@vectorize([float64(float64, float64)])
def f(x, y):
return x + y
```
如果您傳遞了多個簽名,請注意必須在最不具體的簽名之前傳遞大多數特定簽名(例如,在雙精度浮點數之前單精度浮點數),否則基于類型的分派將無法按預期工作:
```py
@vectorize([int32(int32, int32),
int64(int64, int64),
float32(float32, float32),
float64(float64, float64)])
def f(x, y):
return x + y
```
該函數將按預期在指定的數組類型上工作:
```py
>>> a = np.arange(6)
>>> f(a, a)
array([ 0, 2, 4, 6, 8, 10])
>>> a = np.linspace(0, 1, 6)
>>> f(a, a)
array([ 0\. , 0.4, 0.8, 1.2, 1.6, 2\. ])
```
但它將無法在其他類型上工作:
```py
>>> a = np.linspace(0, 1+1j, 6)
>>> f(a, a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: ufunc 'ufunc' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
```
你可能會問自己,“為什么我要經歷這個而不是使用 [@jit](jit.html#jit) 裝飾器編譯一個簡單的迭代循環?”。答案是 NumPy ufuncs 會自動獲得其他功能,如縮小,累積或廣播。使用上面的例子:
```py
>>> a = np.arange(12).reshape(3, 4)
>>> a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> f.reduce(a, axis=0)
array([12, 15, 18, 21])
>>> f.reduce(a, axis=1)
array([ 6, 22, 38])
>>> f.accumulate(a)
array([[ 0, 1, 2, 3],
[ 4, 6, 8, 10],
[12, 15, 18, 21]])
>>> f.accumulate(a, axis=1)
array([[ 0, 1, 3, 6],
[ 4, 9, 15, 22],
[ 8, 17, 27, 38]])
```
也可以看看
[ufuncs 的標準功能](http://docs.scipy.org/doc/numpy/reference/ufuncs.html#ufunc)(NumPy 文檔)。
[`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 裝飾器支持多個 ufunc 目標:
| 目標 | 描述 |
| --- | --- |
| 中央處理器 | 單線程 CPU |
| 平行 | 多核 CPU |
| CUDA | CUDA GPU 注意這會創建一個類似 _ufunc 的 _ 對象。有關詳細信息,請參閱 CUDA ufunc 的[文檔。](../cuda/ufunc.html) |
一般準則是為不同的數據大小和算法選擇不同的目標。 “cpu”目標適用于小數據大小(約小于 1KB)和低計算強度算法。它具有最少的開銷。 “并行”目標適用于中等數據大小(大約小于 1MB)。線程增加了一點延遲。 “cuda”目標適用于大數據量(大約 1MB)和高計算強度算法。向 GPU 傳輸內存和從 GPU 傳輸內存會增加大量開銷。
## 1.6.2。 `@guvectorize`裝飾器
雖然 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 允許你一次寫一個元素的 ufunc,但 [`guvectorize()`](../reference/jit-compilation.html#numba.guvectorize "numba.guvectorize") 裝飾器更進一步,并允許你編寫可以工作的 ufuncs 在輸入數組的任意數量的元素上,并獲取和返回不同維度的數組。典型的例子是運行中值或卷積濾波器。
與 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 函數相反, [`guvectorize()`](../reference/jit-compilation.html#numba.guvectorize "numba.guvectorize") 函數不返回其結果值:它們將其作為數組參數,必須由函數填充。這是因為數組實際上是由 NumPy 的調度機制分配的,調用機制調用 Numba 生成的代碼。
這是一個非常簡單的例子:
```py
@guvectorize([(int64[:], int64, int64[:])], '(n),()->(n)')
def g(x, y, res):
for i in range(x.shape[0]):
res[i] = x[i] + y
```
底層的 Python 函數只是將一個給定的標量(`y`)添加到一維數組的所有元素中。宣言更有意思。那里有兩件事:
* 輸入和輸出 _ 布局 _ 的聲明,符號形式:`(n),()->(n)`告訴 NumPy 該函數采用 _n_ 元素一維數組,一個標量(用符號表示為空元組`()`)并返回 _n_ 元素一維數組;
* `@vectorize`中支持的具體 _ 簽名 _ 列表;這里我們只支持`int64`數組。
注意
1D 數組類型也可以接收標量參數(形狀為`()`的參數)。在上面的例子中,第二個參數也可以聲明為`int64[:]`。在這種情況下,該值必須由`y[0]`讀取。
我們現在可以通過一個簡單的例子來檢查已編譯的 ufunc 的作用:
```py
>>> a = np.arange(5)
>>> a
array([0, 1, 2, 3, 4])
>>> g(a, 2)
array([2, 3, 4, 5, 6])
```
好處是 NumPy 將根據其形狀自動調度更復雜的輸入:
```py
>>> a = np.arange(6).reshape(2, 3)
>>> a
array([[0, 1, 2],
[3, 4, 5]])
>>> g(a, 10)
array([[10, 11, 12],
[13, 14, 15]])
>>> g(a, np.array([10, 20]))
array([[10, 11, 12],
[23, 24, 25]])
```
注意
[`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 和 [`guvectorize()`](../reference/jit-compilation.html#numba.guvectorize "numba.guvectorize") 都支持傳遞`nopython=True` [,如同@jit 裝飾器](jit.html#jit-nopython)。使用它來確保生成的代碼不會回退到[對象模式](../glossary.html#term-object-mode)。
## 1.6.3。動態通用功能
如上所述,如果您沒有將任何簽名傳遞給 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 裝飾器,您的 Python 函數將用于構建動態通用函數,或 [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") 。例如:
```py
from numba import vectorize
@vectorize
def f(x, y):
return x * y
```
結果`f()`是 [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") 實例,以沒有支持的輸入類型開頭。在調用`f()`時,只要傳遞以前不支持的輸入類型,Numba 就會生成新的內核。鑒于上面的示例,以下一組解釋器交互說明了動態編譯的工作原理:
```py
>>> f
<numba._DUFunc 'f'>
>>> f.ufunc
<ufunc 'f'>
>>> f.ufunc.types
[]
```
上面的例子顯示 [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") 實例不是 ufunc。而不是子類 ufunc, [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") 實例通過保持 [`ufunc`](../reference/jit-compilation.html#numba.DUFunc.ufunc "numba.DUFunc.ufunc") 成員,然后將 ufunc 屬性讀取和方法調用委托給此成員(也稱為類型聚合)來工作。當我們查看 ufunc 支持的初始類型時,我們可以驗證沒有。
我們試著打電話給`f()`:
```py
>>> f(3,4)
12
>>> f.types # shorthand for f.ufunc.types
['ll->l']
```
如果這是一個普通的 Numpy ufunc,我們會看到一個異常抱怨 ufunc 無法處理輸入類型。當我們用整數參數調用`f()`時,我們不僅會收到答案,而且我們可以驗證 Numba 是否創建了支持 C `long`整數的循環。
我們可以通過使用不同的輸入調用`f()`來添加其他循環:
```py
>>> f(1.,2.)
2.0
>>> f.types
['ll->l', 'dd->d']
```
我們現在可以驗證 Numba 是否為處理浮點輸入添加了第二個循環`"dd->d"`。
如果我們將輸入類型混合到`f()`,我們可以驗證 [Numpy ufunc 強制轉換規則](http://docs.scipy.org/doc/numpy/reference/ufuncs.html#casting-rules)是否仍然有效:
```py
>>> f(1,2.)
2.0
>>> f.types
['ll->l', 'dd->d']
```
此示例演示了使用混合類型調用`f()`會導致 Numpy 選擇浮點循環,并將整數參數轉換為浮點值。因此,Numba 沒有創建一個特殊的`"dl->d"`內核。
這 [`DUFunc`](../reference/jit-compilation.html#numba.DUFunc "numba.DUFunc") 行為導致我們得到類似于上面“ [@vectorize 裝飾器](#the-vectorize-decorator)”小節中給出的警告的點,但是在裝飾器中沒有簽名聲明順序,呼叫順序很重要。如果我們首先傳入浮點參數,那么任何帶有整數參數的調用都將被轉換為雙精度浮點值。例如:
```py
>>> @vectorize
... def g(a, b): return a / b
...
>>> g(2.,3.)
0.66666666666666663
>>> g(2,3)
0.66666666666666663
>>> g.types
['dd->d']
```
如果您需要對各種類型簽名的精確支持,您應該在 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 裝飾器中指定它們,而不是依賴于動態編譯。
- 1. 用戶手冊
- 1.1。 Numba 的約 5 分鐘指南
- 1.2。概述
- 1.3。安裝
- 1.4。使用@jit 編譯 Python 代碼
- 1.5。使用@generated_jit 進行靈活的專業化
- 1.6。創建 Numpy 通用函數
- 1.7。用@jitclass 編譯 python 類
- 1.8。使用@cfunc 創建 C 回調
- 1.9。提前編譯代碼
- 1.10。使用@jit 自動并行化
- 1.11。使用@stencil裝飾器
- 1.12。從 JIT 代碼 中回調到 Python 解釋器
- 1.13。性能提示
- 1.14。線程層
- 1.15。故障排除和提示
- 1.16。常見問題
- 1.17。示例
- 1.18。會談和教程
- 2. 參考手冊
- 2.1。類型和簽名
- 2.2。即時編譯
- 2.3。提前編譯
- 2.4。公用事業
- 2.5。環境變量
- 2.6。支持的 Python 功能
- 2.7。支持的 NumPy 功能
- 2.8。與 Python 語義的偏差
- 2.9。浮點陷阱
- 2.10。 Python 2.7 壽命終止計劃
- 3. 用于 CUDA GPU 的 Numba
- 3.1。概述
- 3.2。編寫 CUDA 內核
- 3.3。內存管理
- 3.4。編寫設備功能
- 3.5。 CUDA Python 中支持的 Python 功能
- 3.6。支持的原子操作
- 3.7。隨機數生成
- 3.8。設備管理
- 3.10。示例
- 3.11。使用 CUDA 模擬器 調試 CUDA Python
- 3.12。 GPU 減少
- 3.13。 CUDA Ufuncs 和廣義 Ufuncs
- 3.14。共享 CUDA 內存
- 3.15。 CUDA 陣列接口
- 3.16。 CUDA 常見問題
- 4. CUDA Python 參考
- 4.1。 CUDA 主機 API
- 4.2。 CUDA 內核 API
- 4.3。內存管理
- 5. 用于 AMD ROC GPU 的 Numba
- 5.1。概述
- 5.2。編寫 HSA 內核
- 5.3。內存管理
- 5.4。編寫設備功能
- 5.5。支持的原子操作
- 5.6。代理商
- 5.7。 ROC Ufuncs 和廣義 Ufuncs
- 5.8。示例
- 6. 擴展 Numba
- 6.1。高級擴展 API
- 6.2。低級擴展 API
- 6.3。示例:間隔類型
- 7. 開發者手冊
- 7.1。貢獻給 Numba
- 7.2。 Numba 建筑
- 7.3。多態調度
- 7.4。關于發電機的注意事項
- 7.5。關于 Numba Runtime 的注意事項
- 7.6。使用 Numba Rewrite Pass 獲得樂趣和優化
- 7.7。實時變量分析
- 7.8。上市
- 7.9。模板注釋
- 7.10。關于自定義管道的注意事項
- 7.11。環境對象
- 7.12。哈希 的注意事項
- 7.13。 Numba 項目路線圖
- 8. Numba 增強建議
- 9. 術語表