cython - 内联来自另一个 cython 包的 cdef 类的 cdef 方法
问题描述
我有一个像这样的类:
cdef class Cls:
cdef func1(self):
pass
如果我在另一个库中使用这个类,我可以内联 func1 这是一个类方法吗?或者我应该找到解决方法(例如,通过创建一个将 Cls 指针作为参数的函数?
解决方案
有好消息也有好消息:其他模块无法进行内联,但您不必支付 Python 函数调用的全部费用。
什么是内联?它由 C 编译器完成:当 C 编译器知道函数的定义时,它可以决定内联它。这有两个优点:
- 您不必支付调用函数的开销
- 它使进一步的优化成为可能。
参见例如:
%%cython -a
ctypedef unsigned long long ull
cdef ull doit(ull a):
return a
def calc_sum_fun():
cdef ull res=0
cdef ull i
for i in range(1000000000):#10**9
res+=doit(i)
return res
>>> %timeit calc_sum_fun()
53.4 ns ± 1.4 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
怎么可能在 53 纳秒内完成 10^9 次加法?因为它没有完成:C 编译器内联cdef doit()
并能够在编译器期间计算循环的结果。因此,在运行时,程序简单地返回预先计算的结果。
从那里很明显,C 编译器将无法从另一个模块内联函数,因为该定义在另一个 c 文件/翻译单元中对其隐藏。例如见:
#simple.pdx:
ctypedef unsigned long long ull
cdef ull doit(ull a)
#simple.pyx:
cdef ull doit(ull a):
return a
def doit_slow(a):
return a
现在从另一个 cython 模块访问它:
%%cython
cimport simple
ctypedef unsigned long long ull
def calc_sum_fun():
cdef ull res=0
cdef ull i
for i in range(10000000):#10**7
res+=doit(i)
return res
导致以下时间:
>>> %timeit calc_sum_fun()
17.8 ms ± 208 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
因为内联是不可能的,所以该函数确实必须执行循环......但是,它比普通的 python 调用更快,我们可以通过替换cdef doit()
through来做到这一点def doit_slow()
:
%%cython
import simple #import, not cimport
ctypedef unsigned long long ull
def calc_sum_fun_slow():
cdef ull res=0
cdef ull i
for i in range(10000000):#10**7
res+=simple.doit_slow(i) #slow
return res
Python 调用慢了大约 50 倍!
>>> %timeit calc_sum_fun_slow()
1.07 s ± 20.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
但是你问的是类方法而不是全局函数。对于类方法,即使在同一个模块中也无法进行内联:
%%cython
ctypedef unsigned long long ull
cdef class A:
cdef ull doit(self, ull a):
return a
def calc_sum_class():
cdef ull res=0
cdef ull i
cdef A a=A()
for i in range(10000000):#10**7
res+=a.doit(i)
return res
导致:
>>> %timeit calc_sum_class()
18.2 ms ± 264 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
这与在另一个模块中定义 cdef 类的情况基本相同。
这种行为的原因是 cdef 类的构建方式。它与 C++ 中的虚拟类有很大不同——类定义类似于一个名为的虚拟表__pyx_vtab
:
struct __pyx_obj_12simple_class_A {
PyObject_HEAD
struct __pyx_vtabstruct_12simple_class_A *__pyx_vtab;
};
cdef doit()
保存指针的位置:
struct __pyx_vtabstruct_12simple_class_A {
__pyx_t_12simple_class_ull (*doit)(struct __pyx_obj_12simple_class_A *, __pyx_t_12simple_class_ull);
};
当我们调用时,a.doit()
我们不直接调用函数,而是通过这个指针:
((struct __pyx_vtabstruct_12simple_class_A *)__pyx_v_a->__pyx_vtab)->doit(__pyx_v_a, __pyx_v_i);
这就解释了为什么 C 编译器不能内联函数doit()
。
推荐阅读
- javascript - 从相等元素创建双精度数组
- java - 如何通过代理重定向 javax.ws.rs.client.WebTarget Post 请求
- javascript - How to enable disabled inputs when multiple option is selected using javascript?
- asp.net - 在 ASP.NET 中使用 IdentityUser 进行电子邮件验证
- typescript - TS2740 Type is missing the following properties from ReadOnly
error in React Native with TypeScript app - javascript - 上标 .toFixed() 的结果
- c++ - 是否可以将“QStringList”转换为“无符号字符”?
- php - How to check if variable is empty, and give back the same flash message with multiple variables
- android - 找不到联系人应用程序的 android.provider.CONTACTS_STRUCTURE 元数据
- javascript - 为什么 moment.unix(0) 返回 1 小时?