# 7.4。關于發電機的注意事項
> 原文: [http://numba.pydata.org/numba-doc/latest/developer/generators.html](http://numba.pydata.org/numba-doc/latest/developer/generators.html)
Numba 最近獲得了編譯發電機功能的支持。本文檔解釋了一些實現選擇。
## 7.4.1。術語
為清楚起見,我們區分 _ 發生器功能 _ 和 _ 發生器 _。生成器函數是包含一個或多個`yield`語句的函數。生成器(有時也稱為“生成器迭代器”)是生成器函數的返回值;每次調用 [`next()`](https://docs.python.org/3/library/functions.html#next "(in Python v3.7)") 時,它會在其幀內恢復執行。
_ 屈服點 _ 是調用`yield`語句的地方。 _ 恢復點 _ 就在 _ 屈服點 _ 之后的位置,其中當再次調用 [`next()`](https://docs.python.org/3/library/functions.html#next "(in Python v3.7)") 時恢復執行。
## 7.4.2。功能分析
假設我們有以下簡單的生成器函數:
```py
def gen(x, y):
yield x + y
yield x - y
```
這是它的 CPython 字節碼,使用 [`dis.dis()`](https://docs.python.org/3/library/dis.html#dis.dis "(in Python v3.7)") 打印出來:
```py
7 0 LOAD_FAST 0 (x)
3 LOAD_FAST 1 (y)
6 BINARY_ADD
7 YIELD_VALUE
8 POP_TOP
8 9 LOAD_FAST 0 (x)
12 LOAD_FAST 1 (y)
15 BINARY_SUBTRACT
16 YIELD_VALUE
17 POP_TOP
18 LOAD_CONST 0 (None)
21 RETURN_VALUE
```
在 [`NUMBA_DUMP_IR`](../reference/envvars.html#envvar-NUMBA_DUMP_IR) 設置為 1 的情況下編譯此功能時,將打印出以下信息:
```py
----------------------------------IR DUMP: gen----------------------------------
label 0:
x = arg(0, name=x) ['x']
y = arg(1, name=y) ['y']
$0.3 = x + y ['$0.3', 'x', 'y']
$0.4 = yield $0.3 ['$0.3', '$0.4']
del $0.4 []
del $0.3 []
$0.7 = x - y ['$0.7', 'x', 'y']
del y []
del x []
$0.8 = yield $0.7 ['$0.7', '$0.8']
del $0.8 []
del $0.7 []
$const0.9 = const(NoneType, None) ['$const0.9']
$0.10 = cast(value=$const0.9) ['$0.10', '$const0.9']
del $const0.9 []
return $0.10 ['$0.10']
------------------------------GENERATOR INFO: gen-------------------------------
generator state variables: ['$0.3', '$0.7', 'x', 'y']
yield point #1: live variables = ['x', 'y'], weak live variables = ['$0.3']
yield point #2: live variables = [], weak live variables = ['$0.7']
```
這是什么意思?第一部分是 Numba IR,如[第 2 階段:生成 Numba IR](architecture.html#arch-generate-numba-ir) 所見。我們可以看到兩個屈服點(`yield $0.3`和`yield $0.7`)。
第二部分顯示了特定于發電機的信息。要理解它,我們必須了解暫停和恢復生成器的含義。
掛起生成器時,我們不僅僅向調用者返回一個值(`yield`語句的操作數)。我們還必須保存發生器的 _ 當前狀態 _ 以便恢復執行。在簡單的用例中,可能會保留 CPU 的寄存器值或堆棧槽,直到下一次調用 next()。但是,任何非平凡的案例都會無可救藥地破壞這些價值觀,因此我們必須將它們保存在一個定義明確的地方。
我們需要保存哪些值?那么,在 Numba Intermediate Representation 的背景下,我們必須在每個屈服點保存所有 _ 實時變量 _。由于控制流程圖,計算了這些實時變量。
保存實時變量并暫停生成器后,恢復生成器只需執行逆操作:實時變量將從保存的生成器狀態恢復。
注意
這是相同的分析,有助于在適當的地方插入 Numba `del`指令。
讓我們再次查看生成器信息:
```py
generator state variables: ['$0.3', '$0.7', 'x', 'y']
yield point #1: live variables = ['x', 'y'], weak live variables = ['$0.3']
yield point #2: live variables = [], weak live variables = ['$0.7']
```
Numba 計算了所有實時變量的并集(表示為“狀態變量”)。這將有助于定義[發生器結構](#generator-structure)的布局。此外,對于每個屈服點,我們計算了兩組變量:
* _ 實時變量 _ 是恢復點之后的代碼使用的變量(即在`yield`語句之后)
* _ 弱活變量 _ 是在恢復點之后立即進行定義的變量;它們必須保存在[對象模式](../glossary.html#term-object-mode)中,以確保正確的參考清理
## 7.4.3。發電機結構
### 7.4.3.1。布局
功能分析有助于我們收集足夠的信息來定義生成器結構的布局,該布局將存儲生成器的整個執行狀態。這是生成器結構布局的草圖,用偽代碼表示:
```py
struct gen_struct_t {
int32_t resume_index;
struct gen_args_t {
arg_0_t arg0;
arg_1_t arg1;
...
arg_N_t argN;
}
struct gen_state_t {
state_0_t state_var0;
state_1_t state_var1;
...
state_N_t state_varN;
}
}
```
讓我們按順序描述這些字段。
* 第一個成員 _ 恢復索引 _ 是一個整數,告訴生成器必須恢復恢復點執行。按照慣例,它可以有兩個特殊值:0 表示執行必須從生成器的開頭開始(即第一次調用 [`next()`](https://docs.python.org/3/library/functions.html#next "(in Python v3.7)") ); -1 表示生成器已耗盡,并且恢復必須立即引發 StopIteration。其他值表示屈服點的指數從 1 開始(對應于上面的發電機信息中顯示的指數)。
* 第二個成員,_ 參數結構 _ 在首次初始化后是只讀的。它存儲調用生成器函數的參數的值。在我們的例子中,這些是`x`和`y`的值。
* 第三個成員,_ 狀態結構 _,存儲如上計算的實時變量。
具體來說,我們的示例的生成器結構(假設生成器函數使用浮點數調用)是:
```py
struct gen_struct_t {
int32_t resume_index;
struct gen_args_t {
double arg0;
double arg1;
}
struct gen_state_t {
double $0.3;
double $0.7;
double x;
double y;
}
}
```
請注意,此處保存`x`和`y`是多余的:Numba 無法識別狀態變量`x`和`y`與`arg0`和`arg1`具有相同的值。
### 7.4.3.2。分配
Numba 如何確保發電機結構保持足夠長的時間?有兩種情況:
* 當從 Numba 編譯的函數調用 Numba 編譯的生成器函數時,該結構由被調用者在堆棧上分配。在這種情況下,發電機實例化實際上是無成本的。
* 當從常規 Python 代碼調用 Numba 編譯的生成器函數時,實例化 CPython 兼容的包裝器,其具有適當的分配空間來存儲結構,并且其 [`tp_iternext`](https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_iternext "(in Python v3.7)") 插槽是一個包裝器生成器的本機代碼。
## 7.4.4。編譯為本機代碼
在編譯生成器函數時,Numba 實際生成了三個本機函數:
* 初始化函數。這是與生成器函數本身對應的函數:它接收函數參數并將它們存儲在生成器結構(由指針傳遞)中。它還將 _ 恢復指數 _ 初始化為 0,表明發電機尚未啟動。
* next()函數。這是在生成器內恢復執行的函數。它的單個參數是指向生成器結構的指針,它返回下一個產生的值(如果生成器耗盡,則使用特殊的退出代碼,以便在從 Numba 編譯的函數調用時進行快速檢查)。
* 可選的終結器。在對象模式下,此功能可確保存儲在生成器狀態中的所有實時變量都被減少,即使生成器在沒有耗盡的情況下被銷毀也是如此。
### 7.4.4.1。 next()函數
next()函數是三個本機函數中最不直接的函數。它以蹦床開始,根據存儲在發生器結構中的 _ 恢復索引 _,將執行分配到右恢復點。以下是函數 start 在我們的示例中的外觀:
```py
define i32 @"__main__.gen.next"(
double* nocapture %retptr,
{ i8*, i32 }** nocapture readnone %excinfo,
i8* nocapture readnone %env,
{ i32, { double, double }, { double, double, double, double } }* nocapture %arg.gen)
{
entry:
%gen.resume_index = getelementptr { i32, { double, double }, { double, double, double, double } }* %arg.gen, i64 0, i32 0
%.47 = load i32* %gen.resume_index, align 4
switch i32 %.47, label %stop_iteration [
i32 0, label %B0
i32 1, label %generator_resume1
i32 2, label %generator_resume2
]
; rest of the function snipped
```
(從 LLVM IR 修剪的無趣的東西,使其更具可讀性)
我們在`%arg.gen`中識別出指向生成器結構的指針。蹦床開關有三個目標(每個 _ 恢復指數 _ 0,1 和 2),以及一個名為`stop_iteration`的后退目標標簽。標簽`B0`表示函數的開始,`generator_resume1`(相應`generator_resume2`)是第一個(相應的第二個)屈服點之后的恢復點。
在 LLVM 生成之后,此函數的整個本機匯編程序代碼可能如下所示(在 x86-64 上):
```py
.globl __main__.gen.next
.align 16, 0x90
__main__.gen.next:
movl (%rcx), %eax
cmpl $2, %eax
je .LBB1_5
cmpl $1, %eax
jne .LBB1_2
movsd 40(%rcx), %xmm0
subsd 48(%rcx), %xmm0
movl $2, (%rcx)
movsd %xmm0, (%rdi)
xorl %eax, %eax
retq
.LBB1_5:
movl $-1, (%rcx)
jmp .LBB1_6
.LBB1_2:
testl %eax, %eax
jne .LBB1_6
movsd 8(%rcx), %xmm0
movsd 16(%rcx), %xmm1
movaps %xmm0, %xmm2
addsd %xmm1, %xmm2
movsd %xmm1, 48(%rcx)
movsd %xmm0, 40(%rcx)
movl $1, (%rcx)
movsd %xmm2, (%rdi)
xorl %eax, %eax
retq
.LBB1_6:
movl $-3, %eax
retq
```
注意,函數返回 0 表示產生一個值,-3 表示 StopIteration。 `%rcx`指向生成恢復索引的生成器結構的開始。
- 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. 術語表