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