NBEP 3: JIT 类
- 作者:
林思宽
- 日期:
2015年12月
- 状态:
草稿
介绍
Numba 尚不支持用户定义的类。类在使用得当时提供了有用的抽象并促进了模块化。从最简单的意义上说,类分别指定了数据和操作作为属性和方法。类实例是该类的一个实例化。此提案将专注于支持类的这种简单用例——仅包含属性和方法。其他功能,如类方法、静态方法和继承,将推迟到另一个提案中,但我们相信,在本文描述的基础上,这些功能可以轻松实现。
提案:jit-类
JIT 类比 Python 类有更多的限制。我们将重点关注对类及其实例的以下操作:
实例化:使用类对象作为构造函数创建类的实例:
cls(*args, **kwargs)
销毁:移除实例化期间分配的资源,并释放所有对其他对象的引用。
属性访问:使用
instance.attr
语法加载和存储属性。方法访问:使用
instance.method
语法加载方法。
通过这些操作,类对象(不是实例)不需要具体化。在编译器的类型检查阶段,使用类对象作为构造函数是完全解析的(选择运行时实现)。这意味着 类对象不会是一等对象。另一方面,实现一等类对象将需要一个“接口”类型,或类的类型。
类的实例化将为存储数据属性分配资源。这在“存储模型”部分中进行了描述。方法永远不会存储在实例中。它们是附加到类的信息。由于类对象仅存在于类型域中,因此方法也将在类型阶段完全解析。同样,numba 没有第一类函数值,并且每个函数类型都唯一映射到每个函数实现(这需要更改以支持将函数值作为参数)。
一个类实例可以包含其他 NRT 引用计数的对象作为属性。为了正确清理实例,当实例的引用计数降至零时,会调用析构函数。这在“引用计数和析构函数”部分中进行了描述。
存储模型
为了与C兼容,属性存储在一个简单的普通数据结构中。每个属性按照用户定义的顺序存储在一个填充(以确保正确对齐)的连续内存区域中。包含三个字段(int32、float32、complex64)的实例将与以下C结构兼容:
struct {
int32 field0;
float32 field1;
complex64 field2;
};
这也将与对齐的 NumPy 结构化 dtype 兼容。
方法
方法是可绑定到实例的常规函数。它们可以被 numba 编译为常规函数。操作 getattr(instance, name)``(从 ``instance
获取属性 name
)在运行时将实例绑定到请求的方法。
特殊的 __init__
方法也像常规函数一样处理。
__del__
目前不支持。
引用计数和析构函数
jit-class 的一个实例由 NRT 进行引用计数。由于它可能包含其他 NRT 跟踪的对象,因此当其引用计数降为零时,必须调用析构函数。析构函数会将所有属性的引用计数减一。
目前,不支持用户定义的 __del__
方法。
目前不处理循环引用的适当清理。循环会导致内存泄漏。
类型推断
到目前为止,我们还没有描述属性和方法的类型。类型信息对于实例化(例如分配存储空间)是必要的。最简单的方法是让用户提供每个属性的类型以及它们的顺序;例如:
dct = OrderedDict()
dct['x'] = int32
dct['y'] = float32
允许用户提供一个有序字典将提供属性的名称、顺序和类型。然而,这种静态类型的语义不如Python语义灵活,后者表现得像一个泛型类。
推断属性的类型是困难的。在之前尝试实现JIT类的过程中,__init__
方法被专门化以捕获存储在属性中的类型。由于该方法可以包含任意逻辑,如果类型根据值有条件地分配,问题可能会变成一个依赖类型问题。(很少有语言实现依赖类型,而那些实现了的语言大多是定理证明器。)
示例:使用 OrderedDict 输入函数
spec = OrderedDict()
spec['x'] = numba.int32
spec['y'] = numba.float32
@jitclass(spec)
class Vec(object):
def __init__(self, x, y):
self.x = x
self.y = y
def add(self, dx, dy):
self.x += dx
self.y += dy
示例:使用2元组列表输入函数
spec = [('x', numba.int32),
('y', numba.float32)]
@jitclass(spec)
class Vec(object):
...
从单个类对象创建多个 jitclass
jitclass(spec) 装饰器即使应用于同一个类对象和相同的类型规范,也会创建一个新的 jitclass 类型。
class Vec(object):
...
Vec1 = jitclass(spec)(Vec)
Vec2 = jitclass(spec)(Vec)
# Vec1 and Vec2 are two different jitclass types
从解释器中使用
在构建一个新的 jitclass 实例时,会创建一个“盒子”来包装来自 numba 的基础 jitclass 实例。属性与方法可以从解释器中访问。实际的实现将在 numba 编译的代码中。任何 Python 对象都会被转换为其在 numba 中的原生表示形式以供使用。同样,返回值也会被转换为其 Python 表示形式。因此,在解释器中操作 jitclass 实例可能会有开销。这种开销很小,并且应该很容易被编译方法中更高效的计算所分摊。
对属性、静态方法和类方法的支持
property
的使用仅限于 getter 和 setter。Deleter 不受支持。
不支持使用 staticmethod
。
不支持使用 classmethod
。
继承
在此提案中不考虑类继承。jitclass 唯一接受的基类是 object。
支持的目标
仅支持 CPU 目标(包括并行目标)。GPU(例如 CUDA 和 HSA)目标通过 jitclass 实例的不可变版本支持,这将在单独的 NBEP 中描述。
其他属性
给定:
spec = [('x', numba.int32),
('y', numba.float32)]
@jitclass(spec)
class Vec(object):
...
isinstance(Vec(1, 2), Vec)
为 True。type(Vec(1, 2))
可能不是Vec
。
未来的增强功能
本提案仅描述了 jitclass 的基本语义和功能。其他功能将在未来的增强提案中描述。