栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

cpdef和包裹在def中的cdef之间有什么区别?

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

cpdef和包裹在def中的cdef之间有什么区别?

chrisb的答案为您提供了所有您需要知道的信息,但是如果您想了解血腥细节…

但是首先,从冗长的分析中总结出来的要点是:

  • 对于自由功能,

    cpdef
    使用
    cdef
    +
    def
    性能方面的差异与将其推出没有太大区别。生成的C代码几乎相同。

  • 对于绑定方法,

    cpdef
    在存在继承层次结构的情况下,-approach可能会稍快一些,但没有什么让您感到兴奋的。

  • 使用

    cpdef
    -syntax有其优势,因为生成的代码更清晰(至少对我而言)且更短。


免费功能:

当我们定义一些愚蠢的东西时:

 cpdef do_nothing_cp():   pass

发生以下情况:

  1. 创建了一个快速的c函数(在这种情况下,它具有一个不明确的名称,
    __pyx_f_3foo_do_nothing_cp
    因为我的扩展名为
    foo
    ,但实际上您只需要查找
    f
    前缀)。
  2. 还创建了一个python函数(称为
    __pyx_pf_3foo_2do_nothing_cp
    -prefix
    pf
    ),它不会复制代码,并且不会在途中的某个位置调用fast函数。
  3. 创建了一个python-wrapper,名为
    __pyx_pw_3foo_3do_nothing_cp
    (prefix
    pw
  4. 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()
以下代码时:

  1. do_nothing_cp
    找到绑定到名称的函数指针,在本例中为python-wrapper
    pw
    -function。
  2. pw
    -function通过函数指针进行调用,并调用
    pf
    -function(作为C函数)
  3. 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执行以下操作:

  1. _do_nothing
    创建了一个快速功能,对应于
    f
    上面的功能。
  2. 创建了一个
    pf
    for函数
    do_nothing
    ,该函数会在
    _do_nothing
    途中调用。
  3. python-wrapper,即
    pw
    创建了包装
    pf
    -function的函数
  4. 该功能
    foo.do_nothing
    通过功能指针绑定到python-wrapper
    pw
    -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

乍一看,与上面的情况没有太大区别:

  1. f
    发出该函数的快速的,仅c的-prefix-version
  2. pf
    发出一个python(prefix )版本,该版本调用
    f
    -function
  3. python包装器(前缀
    pw
    )包装
    pf
    -version并用于注册。
  4. 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()
怎样?

  1. 定位并调用了“ B-pw-call_do_nothing”。
  2. 它呼叫
    B-pf-call_do_nothing
  3. 这就要求
    B-f-call_do_nothing
  4. 该调用
    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()
会导致:

  1. call_do_nothing' of the
    定位并调用-class的C
    -class being located and called which means,
    pw-call_do_nothing’
    B
  2. 这就要求
    B-pf-call_do_nothing
  3. 这就要求
    B-f-call_do_nothing
  4. 哪个调用
    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
速度稍快一点,差别并不大。



转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/625188.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号