NBEP 3: JIT 类
- 作者
Siu Kwan Lam
- 日期
2015 年 12 月
- 状态
草稿
简介
Numba 尚未支持用户定义的类。类在正确使用时可提供有用的抽象并提升模块化。最简单地说,类分别将数据和操作集合指定为属性和方法。类实例是该类的一个实例化。本提案将侧重于支持类的这种简单用例——仅包含属性和方法。其他功能,例如类方法、静态方法和继承,将推迟到另一个提案中讨论,但我们相信基于此处描述的基础,这些功能可以轻松实现。
提案: jit-类
JIT 类比 Python 类受到更多限制。我们将重点关注类及其实例的以下操作:
实例化:使用类对象作为构造函数创建类实例:
cls(*args, **kwargs)
销毁:移除实例化期间分配的资源,并释放对其他对象的所有引用。
属性访问:使用
instance.attr
语法加载和存储属性。方法访问:使用
instance.method
语法加载方法。
通过这些操作,类对象(而非实例)无需具体化。在编译器的类型推断阶段,将类对象用作构造函数的操作会得到完全解析(选择一个运行时实现)。这意味着类对象将不是一级对象(first class)。另一方面,实现一级类对象将需要一个“接口”类型,或者说是类的类型。
类的实例化将分配资源用于存储数据属性。这在“存储模型”部分中进行了描述。方法永远不会存储在实例中。它们是附加到类的信息。由于类对象只存在于类型域中,因此方法也将在类型推断阶段完全解析。同样,Numba 没有一级函数值,并且每种函数类型都唯一映射到每个函数实现(这需要更改以支持函数值作为参数)。
类实例可以包含其他 NRT 引用计数的对象作为属性。为了正确清理实例,当实例的引用计数降至零时,会调用析构函数。这在“引用计数和析构函数”部分中进行了描述。
存储模型
为了与 C 兼容,属性存储在简单的“纯旧数据结构”(plain-old-data structure)中。每个属性都以用户定义的顺序存储在经过填充(以确保正确对齐)的连续内存区域中。包含 int32、float32、complex64 三个字段的实例将与以下 C 结构兼容:
struct {
int32 field0;
float32 field1;
complex64 field2;
};
这也将与对齐的 NumPy 结构化数据类型兼容。
方法
方法是可以绑定到实例的常规函数。它们可以由 Numba 编译为常规函数。操作 getattr(instance, name)
(从 instance
获取属性 name
)在运行时将实例绑定到所请求的方法。
特殊的 __init__
方法也像常规函数一样处理。
目前不支持 __del__
。
引用计数和析构函数
JIT 类的实例由 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):
...
从单个类对象创建多个 jit 类
jitclass(spec) 装饰器即使应用于相同的类对象和相同的类型规范,也会创建一个新的 jit 类类型。
class Vec(object):
...
Vec1 = jitclass(spec)(Vec)
Vec2 = jitclass(spec)(Vec)
# Vec1 and Vec2 are two different jitclass types
在解释器中的使用
当构造一个新的 jit 类实例时,会创建一个“箱”(box)来包装 Numba 中的底层 jit 类实例。属性和方法可以从解释器中访问。实际实现将在 Numba 编译代码中。任何 Python 对象都会转换为其原生表示形式以供 Numba 使用。同样,返回值也会转换为其 Python 表示形式。因此,在解释器中操作 jit 类实例可能会产生开销。这种开销是最小的,并且应该通过编译方法中更高效的计算轻松摊销。
支持 property、staticmethod 和 classmethod
property
的使用仅支持 getter 和 setter。不支持 deleter。
不支持 staticmethod
的使用。
不支持 classmethod
的使用。
继承
本提案中不考虑类继承。jit 类唯一接受的基类是 object。
支持的目标
目前仅支持 CPU 目标(包括并行目标)。GPU(例如 CUDA 和 HSA)目标通过 jit 类实例的不可变版本提供支持,这将在单独的 NBEP 中描述。
其他属性
给定
spec = [('x', numba.int32),
('y', numba.float32)]
@jitclass(spec)
class Vec(object):
...
isinstance(Vec(1, 2), Vec)
为 True。type(Vec(1, 2))
可能不是Vec
。
未来增强
本提案仅描述了 jit 类的基本语义和功能。更多特性将在未来的增强提案中描述。