# 7.3。多態調度
> 原文: [http://numba.pydata.org/numba-doc/latest/developer/dispatching.html](http://numba.pydata.org/numba-doc/latest/developer/dispatching.html)
使用 [`jit()`](../reference/jit-compilation.html#numba.jit "numba.jit") 或 [`vectorize()`](../reference/jit-compilation.html#numba.vectorize "numba.vectorize") 編譯的函數是開放式的:可以使用許多不同的輸入類型調用它們,并且必須選擇(可能在運行中編譯)正確的低級專業化。我們在此解釋如何實施這一機制。
## 7.3.1。要求
JIT 編譯的函數可以采用多個參數,并且在選擇特化時會考慮每個參數。因此,它是多調度的一種形式,比單一調度更復雜。
每個參數都根據其 [Numba 類型](../reference/types.html#numba-types)進行選擇。 Numba 類型通常比 Python 類型更精細:例如,Numba 根據其維度和布局(C-contiguous 等)對 Numpy 數組進行不同的類型。
一旦為每個參數推斷出 Numba 類型,就必須在可用的參數中選擇特化;或者,如果找不到合適的專業化,則必須編制新的專業化。這不是一個簡單的決定:可以有多個與給定具體簽名兼容的特化(例如,假設兩個參數函數已經為`(float64, float64)`和`(complex64, complex64)`編譯了特化,并且它用`(float32, float32)`調用)。
因此,調度機制中有兩個關鍵步驟:
1. 推斷 Numba 類型的具體論證
2. 為推斷的 Numba 類型選擇最佳可用專業化(或選擇編譯新的專業化)
### 7.3.1.1。編譯時與運行時
本文檔討論了在運行時完成的調度,即從純 Python 調用 JIT 編譯的函數時。在這種情況下,績效很重要。為了保持 Python 中正常函數調用開銷的范圍,調度的開銷應該保持在一微秒以下。當然,_ 越快越好 _ ......
當從另一個 JIT 編譯的函數調用 JIT 編譯的函數時(在 [nopython 模式](../glossary.html#term-nopython-mode)中),在編譯時使用非性能關鍵機制解析多態,承載零運行時性能開銷。
注意
在實踐中,這里描述的性能關鍵部分用 C 編碼。
## 7.3.2。類型分辨率
因此,第一步是在調用時推斷出每個函數的具體參數的 Numba 類型。鑒于 Numba 類型與 Python 類型相比更精細的粒度,人們不能簡單地查找對象的類并用它鍵入字典以獲得相應的 Numba 類型。
相反,有一種機制來檢查對象,并根據其 Python 類型查詢各種屬性以推斷出適當的 Numba 類型。這可能或多或少復雜:例如,Python `int`參數將始終推斷為 Numba `intp`(指針大小的整數),但 Python `tuple`參數可以推斷出多個 Numba 類型(取決于元組的大小和每個元素的具體類型)。
Numba 類型系統是高級的,用純 Python 編寫;有一個基于泛型函數的純 Python 機制來做推理(在`numba.typing.typeof`中)。該機制用于編譯時推理,例如,關于常數。不幸的是,它對于基于運行時的基于值的調度來說太慢了。它僅用作很少使用(或難以推斷)類型的后備,并且表現出多微秒的開銷。
### 7.3.2.1。類型代碼
Numba 類型系統實際上太高級別,無法從 C 代碼中有效地操作。因此,C 調度層使用基于整數類型代碼的另一種表示。每個 Numba 類型在構造時都會獲得唯一的整數類型代碼;此外,實習系統確保不會創建兩個相同類型的實例。因此,調度層能夠 _ 通過使用簡單的整數類型代碼來避開 _ Numba 類型系統的開銷,這些類型可以適用于眾所周知的優化(快速哈希表等)。
類型解析步驟的目標變為:為每個函數的具體參數推斷 Numba _ 類型代碼 _。理想情況下,它不再處理 Numba 類型......
### 7.3.2.2。硬編碼快速路徑
在避開類型系統的抽象和面向對象開銷的同時,整數類型代碼仍然具有相同的概念復雜性。因此,加速推理的一個重要技術是首先檢查最重要的類型,并為每個類型硬編碼快速解決方案。
有幾種類型可以從這種優化中受益,特別是:
* 基本的 Python 標量(`bool`,`int`,`float`,`complex`);
* 基本的 Numpy 標量(各種整數,浮點數,復數);
* 具有某些維度和基本元素類型的 Numpy 數組。
在幾次簡單檢查之后,每個快速路徑理想地使用硬編碼結果值或直接表查找。
但是,我們不能將該技術應用于所有參數類型;將會出現臨時內部緩存的爆炸性增長,并且難以維護。此外,硬編碼快速路徑的遞歸應用不一定會組合成低開銷(例如,在嵌套元組的情況下)。
### 7.3.2.3。基于指紋的類型代碼緩存
對于非平凡類型(例如,想象一個元組或 Numpy `datetime64`數組),硬編碼的快速路徑不匹配。然后另一個機制開始,更通用。
這里的原則是檢查每個參數值,就像純 Python 機制一樣,并明確地描述它的 Numba 類型。區別在于 _ 我們實際上并不計算 Numba 類型 _。相反,我們計算一個簡單的字節串,這是 Numba 類型的低級可能表示:_ 指紋 _。指紋格式設計得很短,從 C 代碼計算起來非常簡單(實際上,它具有類似字節碼的格式)。
一旦計算了指紋,就會在高速緩存中將指紋映射到 typecodes。緩存是一個哈希表,由于指紋通常非常短(很少超過 20 個字節),因此查找速度很快。
如果緩存查找失敗,則必須首先使用慢速純 Python 機制計算類型代碼。幸運的是,這只會發生一次:在后續調用中,將為給定的指紋返回緩存的類型代碼。
在極少數情況下,無法有效計算指紋。某些類型的情況就是這種情況,這些類型無法從 C 中輕松檢查:例如`cffi`函數指針。然后,在每個函數調用中使用這樣的參數調用緩慢的 Pure Python 機制。
注意
兩個指紋可以表示單個 Numba 類型。這不會使機制不正確;它只會創建更多的緩存條目。
### 7.3.2.4。摘要
函數參數的類型解析按順序包含以下機制:
* 嘗試一些硬編碼的快速路徑,用于常見的簡單類型。
* 如果上述操作失敗,請計算參數的指紋并在緩存中查找其類型代碼。
* 如果上述所有方法都失敗了,請調用純 Python 機制,它將為參數確定 Numba 類型(并查找其類型代碼)。
## 7.3.3。專業化選擇
在上一步中,已為 JIT 編譯函數的每個具體參數確定了整數類型代碼。現在,仍然要將該具體簽名與該函數的每個可用特化項進行匹配。可以有三種結果:
* 有一個令人滿意的最佳匹配:然后調用相應的特化(它將處理參數拆箱和其他細節)。
* 兩個或更多“最佳匹配”之間存在聯系:引發異常,拒絕解決歧義。
* 沒有令人滿意的匹配:為推斷出的具體參數類型編制了新的專業化。
選擇通過循環所有可用的特化,并計算每個具體參數類型與特化的預期簽名中的相應類型的兼容性來工作。具體來說,我們感興趣的是:
1. 是否允許具體參數類型隱式轉換為特化的參數類型;
2. 如果是這樣,轉換的語義(用戶可見)成本。
### 7.3.3.1。隱式轉換規則
從源類型到目標類型有五種可能的隱式轉換(注意這是一種非對稱關系):
1. _ 完全匹配 _:兩種類型相同;這是理想的情況,因為專業化的行為與預期完全一致;
2. _ 同類促銷 _:兩種類型屬于同一“種類”(例如`int32`和`int64`是兩種整數類型),源類型可以無損轉換為目標類型(例如從`int32`到`int64`,但不是相反的);
3. _ 安全轉換 _:這兩種類型屬于不同種類,但源類型可以合理地轉換為目標類型(例如從`int32`到`float64`,但不能反向);
4. _ 不安全轉換 _:從源類型到目標類型可以進行轉換,但可能會失去精度,幅度或其他所需的質量。
5. _ 無轉換 _:沒有正確或合理有效的方法在兩種類型之間進行轉換(例如在`int64`和`datetime64`之間,或者在 C 連續數組和 Fortran 連續數組之間) 。
當檢查專門化時,后兩種情況將其從最終選擇中消除:即,當至少一個參數具有 _ 無轉換 _ 或僅 _ 不安全轉換 _ 到簽名的參數類型時。
注意
但是,如果在 [`jit()`](../reference/jit-compilation.html#numba.jit "numba.jit") 調用中使用顯式簽名編譯函數(因此不允許編譯新的特化),則允許 _ 不安全轉換 _。
### 7.3.3.2。候選人和最佳匹配
如果上述規則沒有消除專業化,它將進入 _ 候選人 _ 列表以供最終選擇。這些候選者按有序的 4-uple 整數排序:`(number of unsafe conversions, number of safe conversions, number of same-kind promotions, number of exact matches)`(注意元組元素的總和等于參數的數量)。最佳匹配是按升序排序的第一個結果,因此更喜歡完全匹配促銷,促銷而不是安全轉化,安全轉換不安全轉化。
### 7.3.3.3。實施
上述機制適用于整數類型代碼,而不適用于 Numba 類型。它使用內部哈希表存儲每對兼容類型的可能轉換類型。內部哈希表部分在啟動時構建(對于內置普通類型,如`int32`,`int64`等),部分動態填充(對于任意復雜類型,如數組類型:例如,允許使用 C 連續的 2D 數組,其中函數需要非連續的 2D 數組。
### 7.3.3.4。摘要
選擇正確的專業化涉及以下步驟:
* 檢查每個可用的特化并將其與具體參數類型進行匹配。
* 消除任何至少有一個參數不能提供足夠兼容性的專業化。
* 如果有剩余的候選者,請在保留類型的語義方面選擇最佳的候選者。
## 7.3.4。雜項
調度性能的一些[基準存在于](https://github.com/numba/numba-benchmark/blob/master/benchmarks/bench_dispatch.py) [Numba 基準測試](https://github.com/numba/numba-benchmark)存儲庫中。
有關機械特定方面的一些單元測試可在`numba.tests.test_typeinfer`和`numba.tests.test_typeof`中找到。更高級別的調度測試在`numba.tests.test_dispatcher`中。
- 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. 術語表