Одной из идей может быть использование numba для создания ufuncs и cffi для компиляции c-кода.
Например, если мы хотим удвоить значениекаждый элемент в массиве numpy, т. е. имеющий следующую C-функцию в виде строки:
double f(double a){
return 2.0*a;
}
, возможное решение - следующий прототип:
import numba as nb
import cffi
def create_ufunc(code):
# 1. step: compile the C-code and load the resulting extension
ffibuilder = cffi.FFI()
ffibuilder.cdef("double f(double a);", override=True)
built_module=ffibuilder.verify(source=code)
fun = built_module.f
# 2. step: create an ufunc out of the compiled C-function
@nb.vectorize([nb.float64(nb.float64)])
def f(x):
return fun(x)
return f
А теперь:
import numpy as np
a=np.arange(6).astype(np.float64)
my_f1=create_ufunc("double f(double a){return 2.0*a;}")
my_f1(a)
# array([ 0., 2., 4., 6., 8., 10.])
или, если мы хотим умножить на 10.0
:
my_f2=create_ufunc("double f(double a){return 10.0*a;}")
# array([ 0., 10., 20., 30., 40., 50.])
Очевидно, показывая, что возможно, этот прототип нуждается в некоторой полировке.Например, хотя и компактный, verify
устарел, и двойной вызов create_ufunc
с одним и тем же кодом приведет к предупреждению.
Еще одна проблема: указанная выше версия не компилируется в режиме nopython, несмотря на то, что функции cffi поддерживаются numba .Не уверен, что здесь происходит не так?Ниже приведен обходной путь: более сложная версия, которая создается в режиме nopython.
Однако, это, вероятно, все еще хорошая отправная точка.
Кажется, что возможно скомпилироватьnumba в режиме nopython, если мы используем out-of-line (compile
) вместо in-line (verify
) API-режим:
import numba as nb
import cffi
import zlib
import importlib
import numba.cffi_support as nbcffi
def create_ufunc(code):
# 1. step: compile the C-code and load the resulting extension
# create a different so/dll for different codes
# and load it
module_name="myufunc"+str(zlib.adler32(code.encode('ascii')))
ffibuilder = cffi.FFI()
ffibuilder.cdef("double f(double a);", override=True)
ffibuilder.set_source(module_name=module_name,source=code)
ffibuilder.compile(verbose=True)
loaded = importlib.import_module(module_name)
# 2. step: create an ufunc out of the compiled C-function
# out-of-line modules must be registered in numba:
nbcffi.register_module(loaded)
fun = loaded.lib.f
@nb.vectorize([nb.float64(nb.float64)], nopython=True)
def f(x):
return fun(x)
return f
Важные сведения:
- Для каждого
code
существует новое расширение (so / pyd-file).Мы различаем их по хеш-значению переданных code
. - . Со временем будет довольно много
myufuncXXXX.so
-файлов, можно подумать о реализации инфраструктуры, аналогичной используемой * 1050.*. ffibuilder.compile(verbose=True)
только для целей отладки, вероятно, verbose=False
имеет больше смысла в выпуске.