# 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. 术语表