从Numba jitted代码调用Cython函数

时间:2017-03-25 12:02:13

标签: python cython numba

我知道调用另一个jitted函数的Numba-jitted函数会识别这个并自动使用快速C调用约定而不是通过Python对象层,因此避免了高Python函数调用开销:

import numba

@numba.jit
def foo(x):
    return x**2

@numba.jit
def bar(x):
    return 4 * foo(x)   # this will be a fast function call

我的问题是,如果我从Numba调用Cython函数,是否也是如此。所以,让我们说我有一个Cython模块,foo.pyx

cpdef double foo(double x):
    return x**2

以及标准Python模块bar.py

import numba
import foo

@numba.jit
def bar(x):
    return 4 * foo.foo(x)   # will this be a fast function call?

Numba会自动将foo.foo识别为C可调用函数,还是需要通过设置CFFI包装来手动告诉它?

编辑:经过进一步的反思,Cython功能只是标准的内置"从Python解释器的角度来看。因此,问题可以更加通用:Numba是否优化对内置函数和方法的调用以绕过Python调用开销?

2 个答案:

答案 0 :(得分:5)

可以在nopython-numba中使用Cython的cpdef / cdef函数(但不能使用def函数)

  1. 步骤:cdef / cpdef函数必须在Cython代码中为marked as api
  2. 步骤:numba.extending.get_cython_function_address可用于获取cpdef函数的地址。
  3. 步骤:ctypes可用于从cpdef函数的地址创建CFunction,该地址可用于numba-nopython代码。

继续阅读,以获得更详细的解释。


即使内置函数(PyCFunction与Cython的def函数)都是用C编写的,它们也没有可被nopython-numba代码使用的签名

例如,acos模块中的math函数没有签名

`double acos(double)`

正如人们所期望的,但是its signature

static PyObject * math_acos(PyObject *self, PyObject *args)

因此,基本上,为了调用此函数,numba需要从手头的C浮点构建一个Python浮点,但是nopython=True禁止这样做。

但是,Cythons cpdef函数有点不同:它是围绕实cdef函数的小包装,其实参为double,{ {1}},依此类推。仅在知道地址的情况下,numba才能使用此int函数。

Cython提供了一种以可移植的方式找出cdef函数的地址的方法:这些地址可以在cythonized模块的属性cdef中找到。

但是,并非所有__pyx_capi__cdef函数都以这种方式公开,而是只有那些显式标记为C-api declarations或通过cpdef共享隐式公开的函数。文件。

一旦pxd的功能foo被标记为foomodule

api

cpdef函数cpdef api double foo(double x): return x*x 的地址可以在foo-字典中找到:

foomodule.__pyx_capi__

在Python中,很难从PyCapsule中提取地址。一种可能是使用ctypes.pythonapi,另一种(可能更简单)是利用Cython访问Python的C-API:

import foomodule
foomodule.__pyx_capi
# {'foo': <capsule object "double (double)" at 0x7fe0a46f0360>}

可以用作:

%%cython
from cpython.pycapsule cimport  PyCapsule_GetPointer, PyCapsule_GetName
def address_from_capsule(object capsule):
    name = PyCapsule_GetName(capsule)
    return <unsigned long long int> PyCapsule_GetPointer(capsule, name)

但是,numba提供了类似的开箱即用功能-get_cython_function_address

addr = address_from_capsule(foomodule.__pyx_capi__['foo'])

一旦获得了c函数的地址,就可以构造一个from numba.extending import get_cython_function_address addr = get_cython_function_address("foomodule", "foo") 函数:

ctypes

例如,可以从nopython-numba中使用此功能,例如:

import ctypes
foo_functype = ctypes.CFUNCTYPE(ctypes.c_double, ctypes.c_double)
foo_for_numba = foo_functype(addr)

现在:

from numba import njit
@njit
def use_foo(x):
    return foo_for_numba(x)

产生预期的结果。

答案 1 :(得分:4)

有一组有限的内置函数(来自python标准库和numpy),numba知道如何转换为本机代码:

http://numba.pydata.org/numba-doc/latest/reference/pysupported.html http://numba.pydata.org/numba-doc/latest/reference/numpysupported.html

Numba无法在nopython模式下进行任何其他操作,因此使用速度慢得多的objectmode

没有直接的方法可以将cython函数传递给Numba并在nopython模式下识别它。 Numba确实有cffi的钩子:

http://numba.pydata.org/numba-doc/latest/reference/pysupported.html#cffi

可以用来调用外部C代码,如果你可以在C级创建一个低级别的包装器,你可以装备调用cython;我不能100%确定这是否可行。我写过这样做是为了从Numba调用RMath函数:

https://www.continuum.io/blog/developer-blog/calling-c-libraries-numba-using-cffi

如果你走这条路,这可能对你有所帮助。