chrisb的答案为您提供了所有您需要知道的信息,但是如果您想了解血腥细节…
但是首先,从冗长的分析中总结出来的要点是:
对于自由功能,
cpdef
使用cdef
+def
性能方面的差异与将其推出没有太大区别。生成的C代码几乎相同。对于绑定方法,
cpdef
在存在继承层次结构的情况下,-approach可能会稍快一些,但没有什么让您感到兴奋的。使用
cpdef
-syntax有其优势,因为生成的代码更清晰(至少对我而言)且更短。
免费功能:
当我们定义一些愚蠢的东西时:
cpdef do_nothing_cp(): pass
发生以下情况:
- 创建了一个快速的c函数(在这种情况下,它具有一个不明确的名称,
__pyx_f_3foo_do_nothing_cp
因为我的扩展名为foo
,但实际上您只需要查找f
前缀)。 - 还创建了一个python函数(称为
__pyx_pf_3foo_2do_nothing_cp
-prefixpf
),它不会复制代码,并且不会在途中的某个位置调用fast函数。 - 创建了一个python-wrapper,名为
__pyx_pw_3foo_3do_nothing_cp
(prefixpw
) do_nothing_cp
发出方法定义,这是python-wrapper所需要的,这是存储foo.do_nothing_cp
调用该函数时应调用的地方。
您可以在生成的C代码中查看它:
static PyMethodDef __pyx_methods[] = { {"do_nothing_cp", (PyCFunction)__pyx_pw_3foo_3do_nothing_cp, METH_NOARGS, 0}, {0, 0, 0, 0}};对于
cdef功能,仅发生第一步,对于功能,仅发生
def步骤2-4。
现在,当我们加载模块
foo并调用
foo.do_nothing_cp()以下代码时:
do_nothing_cp
找到绑定到名称的函数指针,在本例中为python-wrapperpw
-function。pw
-function通过函数指针进行调用,并调用pf
-function(作为C函数)pf
功能调用快速f
功能。
如果我们
do_nothing_cp在cython模块内部调用会发生什么?
def call_do_nothing_cp(): do_nothing_cp()
显然,在这种情况下,cython不需要python机器来定位函数-它可以
f通过c函数调用,绕过
pw和
pf函数直接使用快速函数。
如果将
cdef函数包装在
def-function中会怎样?
cdef _do_nothing(): passdef do_nothing(): _do_nothing()
Cython执行以下操作:
_do_nothing
创建了一个快速功能,对应于f
上面的功能。- 创建了一个
pf
for函数do_nothing
,该函数会在_do_nothing
途中调用。 - python-wrapper,即
pw
创建了包装pf
-function的函数 - 该功能
foo.do_nothing
通过功能指针绑定到python-wrapperpw
-function。
如您所见-与-方法没有太大区别
cpdef。
该
cdef-functions只是简单的C函数,但
def和
cpdef功能都是一流的蟒蛇功能-你可以这样做:
foo.do_nothing=foo.do_nothing_cp
关于性能,我们不能期望在这里有太大的区别:
>>> import foo>>> %timeit foo.do_nothing_cp51.6 ns ± 0.437 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)>>> %timeit foo.do_nothing51.8 ns ± 0.369 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
如果查看生成的机器代码(
objdump -d foo.so),我们可以看到C编译器已内联了cpdef-
version的所有调用
do_nothing_cp:
0000000000001340 <__pyx_pw_3foo_3do_nothing_cp>: 1340: 48 8b 05 91 1c 20 00 mov 0x201c91(%rip),%rax 1347: 48 83 00 01 addq $0x1,(%rax) 134b: c3retq 134c: 0f 1f 40 00 nopl 0x0(%rax)
但不适用于推出的产品
do_nothing(我必须承认,我有点惊讶,还不了解原因):
0000000000001380 <__pyx_pw_3foo_1do_nothing>: 1380: 53push %rbx 1381: 48 8b 1d 50 1c 20 00 mov 0x201c50(%rip),%rbx # 202fd8 <_DYNAMIC+0x208> 1388: 48 8b 13 mov (%rbx),%rdx 138b: 48 85 d2 test %rdx,%rdx 138e: 75 0d jne 139d <__pyx_pw_3foo_1do_nothing+0x1d> 1390: 48 8b 43 08 mov 0x8(%rbx),%rax 1394: 48 89 df mov %rbx,%rdi 1397: ff 50 30 callq *0x30(%rax) 139a: 48 8b 13 mov (%rbx),%rdx 139d: 48 83 c2 01 add $0x1,%rdx 13a1: 48 89 d8 mov %rbx,%rax 13a4: 48 89 13 mov %rdx,(%rbx) 13a7: 5bpop %rbx 13a8: c3retq 13a9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
这可以解释为什么
cpdef版本会稍微快一些,但是与python函数调用的开销相比,两者之间没有什么区别。
类方法:
由于可能存在多态性,因此对于类方法而言,情况要复杂一些。让我们开始:
cdef class A: cpdef do_nothing_cp(self): pass
乍一看,与上面的情况没有太大区别:
f
发出该函数的快速的,仅c的-prefix-versionpf
发出一个python(prefix )版本,该版本调用f
-function- python包装器(前缀
pw
)包装pf
-version并用于注册。 do_nothing_cp
被注册为A
通过的tp_methods
指针的类方法PyTypeObject
。
在生成的c文件中可以看到:
static PyMethodDef __pyx_methods_3foo_A[] = { {"do_nothing", (PyCFunction)__pyx_pw_3foo_1A_1do_nothing_cp, METH_NOARGS, 0}, ... {0, 0, 0, 0} }; .... static PyTypeObject __pyx_type_3foo_A = { ... __pyx_methods_3foo_A, ...};显然,绑定版本必须具有隐式参数
self作为附加参数-
但这还有更多:
f-function如果不从相应的
pf函数调用,则执行函数调度,该调度如下所示(我仅保留重要的部分):
static PyObject *__pyx_f_3foo_1A_do_nothing_cp(CYTHON_UNUSED struct __pyx_obj_3foo_A *__pyx_v_self, int __pyx_skip_dispatch) { if (unlikely(__pyx_skip_dispatch)) ;//__pyx_skip_dispatch=1 if called from pf-version else if (look-up if function is overriden in __dict__ of the object) use the overriden function } do the work.为什么需要它?考虑以下扩展名
foo:
cdef class A: cpdef do_nothing_cp(self): passcdef class B(A): cpdef call_do_nothing(self): self.do_nothing()
我们打电话时会
B().call_do_nothing()怎样?
- 定位并调用了“ B-pw-call_do_nothing”。
- 它呼叫
B-pf-call_do_nothing
, - 这就要求
B-f-call_do_nothing
, - 该调用
A-f-do_nothing_cp
,绕过pw
和pf
-versions。
当我们添加以下类
C(覆盖
do_nothing_cp-function)时会发生什么?
import foodef class C(foo.B): def do_nothing_cp(self): print("I do something!")现在致电
C().call_do_nothing()会导致:
call_do_nothing' of the
定位并调用-class的C-class being located and called which means,
pw-call_do_nothing’B
,- 这就要求
B-pf-call_do_nothing
, - 这就要求
B-f-call_do_nothing
, - 哪个调用
A-f-do_nothing
(如我们所知!),绕过pw
和pf
-versions。
现在,在第4步中,我们需要调度呼叫
A-f-do_nothing()以便获得正确的
C.do_nothing()呼叫!幸运的是我们手边的函数中有此调度!
更复杂的是:如果该类
C也是
cdef-class怎么办?
__dict__由于cdef类没有
__dict__?,所以无法通过via进行分派。
对于CDEF类,多态型的实现方式类似于C
++的‘虚拟表’,所以在
B.call_do_nothing()该
f-do_nothing-function不直接调用,但经由一个指针,其取决于对象的类别(一个可以看到这些‘虚拟表’是设定于
__pyx_pymod_exec_XXX,例如
__pyx_vtable_3foo_B.__pyx_base)。因此,
__dict__在
A-f-do_nothing()纯cdef层次结构的情况下,不需要-function中的-dispatch 。
至于性能,
cpdef与
cdef+比较,
def我得到:
cpdef def+cdef A.do_nothing 107ns 108ns B.call_nothing 109ns 116ns
因此,如果有人的话,
cpdef速度稍快一点,差别并不大。



