gpt4 book ai didi

cython - 从另一个cython包的cdef类中内联一个cdef方法

转载 作者:行者123 更新时间:2023-12-04 01:56:27 25 4
gpt4 key购买 nike

我有一个 cython 类,看起来像这样:

cdef class Cls:

cdef func1(self):
pass

如果我在另一个库中使用这个类,我是否可以内联 func1 这是一个类方法?或者我应该找到解决它的方法(例如,通过创建一个将 Cls 指针作为参数的函数?

最佳答案

有坏消息也有好消息:无法从其他模块进行内联,但您不必支付 Python 函数调用的全部费用。

什么是内联?它由 C 编译器完成:当 C 编译器知道函数的定义时,它可以决定将其内联。这有两个好处:

  1. 您不必支付调用函数的开销
  2. 它使进一步优化成为可能。

例如:

%%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() 通过 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()

关于cython - 从另一个cython包的cdef类中内联一个cdef方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50260542/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com