簡體   English   中英

從 Numba 即時代碼調用 Cython 函數

[英]Calling Cython functions from Numba jitted code

我知道調用另一個 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包裝?

編輯:經過進一步思考,從 Python 解釋器的角度來看,Cython 函數只是標准的“內置”函數。 所以問題可以變得更普遍:Numba 是否優化了對內置函數和方法的調用以繞過 Python 調用開銷?

可以在 nopython-numba 中使用 Cython 的cpdef / cdef cpdef (但不是def -functions):

  1. 步驟: cdef / cpdef函數必須在 Cython 代碼中標記為api
  2. 步驟: numba.extending.get_cython_function_address可以用來獲取cpdef-function的地址。
  3. 步驟: ctypes可以用來從cpdef-function的地址創建一個CFunction ,可以在numba-nopython代碼中使用。

繼續閱讀以獲得更詳細的解釋。


即使內置函數( PyCFunction ,與 Cython 的def函數相同)是用 C 編寫的,它們也沒有可供 nopython-numba-code 使用的簽名。

例如來自math模塊的acos函數沒有簽名

`double acos(double)`

正如人們所料,但它的簽名

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

所以基本上為了調用這個函數,numba 需要從手頭的 C-float 構建一個 Python-float,但這被nopython=True禁止。

然而,Cythons cpdef cpdef有點不同:它是一個真正的cdef cpdef的小包裝,其參數是原始 C 類型,如doubleint等。 這個cdef函數可以被 numba 使用,前提是它的地址是已知的。

用Cython提供了一個辦法,找出的地址cdef -functions在便攜方式:地址可在屬性中找到__pyx_capi__的cythonized模塊。

但是,並非所有cdefcpdef函數都以這種方式公開,只有那些明確標記為C-api 聲明或通過pxd文件隱式共享的pxd

一旦foomodule的函數foo被標記為api

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

cpdef 函數foo的地址可以在foomodule.__pyx_capi__ -dictionary 中找到:

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

在 Python 中從PyCapsule中提取地址非常困難。 一種可能性是使用ctypes.pythonapi ,另一種(可能更簡單)是利用 Cython 來訪問 Python 的 C-API:

%%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)

可以用作:

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

但是,numba 提供了一個開箱即用的類似功能 - get_cython_function_address

from numba.extending import get_cython_function_address
addr = get_cython_function_address("foomodule", "foo")

一旦我們得到了 c 函數的地址,我們就可以構造一個ctypes函數:

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

例如,可以從 nopython-numba 使用此函數,如下所示:

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

現在:

use_foo(5)
# 25.0

產生預期的結果。

numba 知道如何將一組有限的內置函數(來自 python 標准庫和 numpy)轉換為本機代碼:

nopython模式下,Numba 將無法執行其他任何操作,因此求助於更慢的objectmode

沒有直接的方法可以將 cython 函數傳遞給 Numba 並使其在nopython模式下被識別。 Numba 確實有 cffi 的鈎子:

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

可以利用它來調用外部 C 代碼,如果您可以在 C 級別創建一個低級包裝器,您可能能夠調用 cython; 不過,我不是 100% 確定這是否可能。 我寫過這樣做是為了從 Numba 調用 RMath 函數:

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

如果你走那條路,它可能會幫助你開始。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM