从Numba jitted类调用Cython扩展类型

时间:2018-03-03 22:05:18

标签: python cython numba

我如何从Numba jitted类中调用Cython扩展类型的方法?下面我的最小示例失败,我在下面记录了错误。我如何修改我的最小例子以使其有效?

感谢您的帮助!!

最小的例子

我有一个Cython模块,shrubbery.pyx

cdef class Shrubbery:

    cdef int height

    def __init__(self, h):
        self.height = h

    def describe(self):
        print('This shrubbery is', self.height, 'tall.')

我有一个设置文件setup.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension('shrubbery', ['shrubbery.pyx'])]

setup(
    name='shrubbery',
    cmdclass={'build_ext': build_ext},
    ext_modules=ext_modules)

我像往常一样将shrubbery.pyx编译成扩展类型(python setup.py build_ext --inplace)。然后我尝试在numba jitted类中使用Shrubbery,如下所示:

from shrubbery import Shrubbery
import numba as nb

spec = [('value', nb.int32)]


@nb.jitclass(spec)
class Bag(object):
    def __init__(self, value):
        self.value = value

    def size(self):
        return self.value

    def mixed_class_method(self):
        __shrubbery = Shrubbery(5)
        __shrubbery.describe()


# pure numba class: works
_b = Bag(value=3)
print(_b.size())

# pure cython extension type: works
__shrubbery = Shrubbery(5)
__shrubbery.describe()

# mix of cython extension type and numba jitted class: fails
_b.mixed_class_method()

错误

/Users/mg/anaconda/bin/python3 test.py
3
('This shrubbery is', 5, 'tall.')
Traceback (most recent call last):
  File "test.py", line 28, in <module>
    _b.mixed_class_method()
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/jitclass/boxing.py", line 62, in wrapper
    return method(*args, **kwargs)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/dispatcher.py", line 330, in _compile_for_args
    raise e
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/dispatcher.py", line 307, in _compile_for_args
    return self.compile(tuple(argtypes))
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/dispatcher.py", line 579, in compile
    cres = self._compiler.compile(args, return_type)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/dispatcher.py", line 80, in compile
    flags=flags, locals=self.locals)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 779, in compile_extra
    return pipeline.compile_extra(func)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 362, in compile_extra
    return self._compile_bytecode()
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 738, in _compile_bytecode
    return self._compile_core()
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 725, in _compile_core
    res = pm.run(self.status)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 248, in run
    raise patched_exception
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 240, in run
    stage()
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 454, in stage_nopython_frontend
    self.locals)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 881, in type_inference_stage
    infer.propagate()
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typeinfer.py", line 846, in propagate
    raise errors[0]
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typeinfer.py", line 137, in propagate
    constraint(typeinfer)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typeinfer.py", line 415, in __call__
    self.resolve(typeinfer, typevars, fnty)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typeinfer.py", line 441, in resolve
    sig = typeinfer.resolve_call(fnty, pos_args, kw_args, literals=literals)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typeinfer.py", line 1115, in resolve_call
    literals=literals)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typing/context.py", line 204, in resolve_function_type
    return func.get_call_type_with_literals(self, args, kws, literals)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/types/functions.py", line 199, in get_call_type_with_literals
    return self.get_call_type(context, args, kws)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/types/functions.py", line 193, in get_call_type
    return self.template(context).apply(args, kws)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typing/templates.py", line 207, in apply
    sig = generic(args, kws)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/jitclass/base.py", line 322, in generic
    sig = disp_type.get_call_type(self.context, args, kws)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/types/functions.py", line 250, in get_call_type
    template, pysig, args, kws = self.dispatcher.get_call_template(args, kws)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/dispatcher.py", line 269, in get_call_template
    self.compile(tuple(args))
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/dispatcher.py", line 579, in compile
    cres = self._compiler.compile(args, return_type)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/dispatcher.py", line 80, in compile
    flags=flags, locals=self.locals)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 779, in compile_extra
    return pipeline.compile_extra(func)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 362, in compile_extra
    return self._compile_bytecode()
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 738, in _compile_bytecode
    return self._compile_core()
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 725, in _compile_core
    res = pm.run(self.status)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 248, in run
    raise patched_exception
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 240, in run
    stage()
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 454, in stage_nopython_frontend
    self.locals)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/compiler.py", line 880, in type_inference_stage
    infer.build_constraint()
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typeinfer.py", line 802, in build_constraint
    self.constrain_statement(inst)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typeinfer.py", line 961, in constrain_statement
    self.typeof_assign(inst)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typeinfer.py", line 1023, in typeof_assign
    self.typeof_global(inst, inst.target, value)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typeinfer.py", line 1119, in typeof_global
    typ = self.resolve_value_type(inst, gvar.value)
  File "/Users/mg/anaconda/lib/python3.6/site-packages/numba/typeinfer.py", line 1042, in resolve_value_type
    raise TypingError(msg, loc=inst.loc)
numba.errors.TypingError: Failed at nopython (nopython frontend)
Failed at nopython (nopython frontend)
Untyped global name 'Shrubbery': cannot determine Numba type of <class 'type'>
File "test.py", line 16
[1] During: resolving callee type: BoundFunction((<class 'numba.types.misc.ClassInstanceType'>, 'mixed_class_method') for instance.jitclass.Bag#7fef29835df8<value:int32>)
[2] During: typing of call at <string> (3)

2 个答案:

答案 0 :(得分:1)

这主要是对CFFI功能可以起作用的评论中的建议的回应。这是事实,但非常限制。

您可以通过C函数指针将Cython cdef函数转换为CFFI函数。此转换必须在Cython中进行。为了在nopython模式下使用Numba,cdef函数不能接受或返回Python对象。这意味着您无法进行Shrubbery课程。只接受/返回C类型的简单函数将起作用

from libc.stdint cimport uintptr_t

cdef void f(int x) nogil:
    with gil:
        print(x+1)

ctypedef void (*void_int_func_pointer)(int)

def get_cffi_f():
    cdef void_int_func_pointer f_ptr = f
    cdef uintptr_t f_ptr_int = <uintptr_t>f_ptr

    from cffi import FFI
    ffi = FFI()
    return ffi.cast('void (*)(int)',f_ptr_int)

在Python中,您调用call get_cffi_f()来获取f的CFFI包装以传递给Numba函数。请注意,我已将该功能声明为nogil并在其中捕获了GIL - 我不能100%确定Numba是否会释放GIL,所以我这样做是为了安全。可能没有必要。

然后,您可以将这些CFFI包装传递给Numba或将它们作为全局变量访问:

import numba as nb
from cy import get_cffi_f

func_global = get_cffi_f()

@nb.jit(nopython=True)
def simple_func(func):
    func(5)
    func_global(6)
    func(7)

@nb.jitclass([('value', nb.int32)])
class Bag(object):
    def __init__(self,value):
        self.value = value

    def mixed_class_method(self,func):
        func(self.value)
        func_global(self.value-1)

simple_func(get_cffi_f())
Bag(3).mixed_class_method(get_cffi_f())

我的观点是,尝试在这里制作类似Cython类的东西是一个失败的原因。

可能有其他方法可以实现相同的目标 - 您可以让Cython使用apipublic生成标题,并将这些标题与CFFI结合使用。

答案 1 :(得分:0)

来自numba docs:

“jitclass的所有方法都被编译成nopython函数.jitclass实例的数据在堆上被分配为C兼容的结构,这样任何编译的函数都可以绕过解释器直接访问底层数据。 “

正如DavidW所指出的,Shrubbery是一种Python类型而不是C类型,所以你不能在jitclass中使用。

你可以点击各个方法。