当前位置: 首页 > 面试题库 >

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

贝自怡
2023-03-14
问题内容

在Cython文档中,有一个示例,其中给出了两种编写C
/ Python混合方法的方法。一个明确的代码,带有一个用于快速C访问的cdef和一个用于从Python访问的包装def:

cdef class Rectangle:
    cdef int x0, y0
    cdef int x1, y1
    def __init__(self, int x0, int y0, int x1, int y1):
        self.x0 = x0; self.y0 = y0; self.x1 = x1; self.y1 = y1
    cdef int _area(self):
        cdef int area
        area = (self.x1 - self.x0) * (self.y1 - self.y0)
        if area < 0:
            area = -area
        return area
    def area(self):
        return self._area()

还有一个使用cpdef:

cdef class Rectangle:
    cdef int x0, y0
    cdef int x1, y1
    def __init__(self, int x0, int y0, int x1, int y1):
        self.x0 = x0; self.y0 = y0; self.x1 = x1; self.y1 = y1
    cpdef int area(self):
        cdef int area
        area = (self.x1 - self.x0) * (self.y1 - self.y0)
        if area < 0:
            area = -area
        return area

我想知道实际的差异是什么。

例如,从C / Python调用时,方法中的哪一个是更快/更慢?

另外,当子类化/重写时,cpdef是否提供其他方法所缺少的东西?


问题答案:

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函数调用,绕过pwpf函数直接使用快速函数。

如果将cdef函数包装在def-function中会怎样?

cdef _do_nothing():
   pass

def do_nothing():
  _do_nothing()

Cython执行以下操作:

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

如您所见-与-方法没有太大区别cpdef

cdef-functions只是简单的C函数,但defcpdef功能都是一流的蟒蛇功能-你可以这样做:

foo.do_nothing=foo.do_nothing_cp

关于性能,我们不能期望在这里有太大的区别:

>>> import foo
>>> %timeit foo.do_nothing_cp
51.6 ns ± 0.437 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

>>> %timeit foo.do_nothing
51.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:   c3                      retq   
    134c:   0f 1f 40 00             nopl   0x0(%rax)

但不适用于推出的产品do_nothing(我必须承认,我有点惊讶,还不了解原因):

0000000000001380 <__pyx_pw_3foo_1do_nothing>:
    1380:   53                      push   %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:   5b                      pop    %rbx
    13a8:   c3                      retq   
    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, /*tp_methods*/
 ...
};

显然,绑定版本必须具有隐式参数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
  /* Check if overridden in Python */
  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):
   pass

cdef 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,绕过pwpf-versions。

当我们添加以下类C(覆盖do_nothing_cp-function)时会发生什么?

import foo
def 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(如我们所知!),绕过pwpf-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 。

至于性能,cpdefcdef+比较,def我得到:

                          cpdef         def+cdef
 A.do_nothing              107ns         108ns 
 B.call_nothing            109ns         116ns

因此,如果有人的话,cpdef速度稍快一点,差别并不大。



 类似资料:
  • 问题内容: 我想知道的区别,而当我宣布一个功能。def和其他之间的区别或多或少很明显。而且我还看到,有时在声明()中添加了返回类型,有时则没有。 我还想知道如何在cython中声明一个字符串变量,因为我不知道,我将其声明为对象。 问题答案: 在Python中声明一个函数。由于Cython基于C运行时,因此您可以使用和。 在C语言层中声明函数。如您所知(或不?),您必须使用C语言定义每个函数的返回值

  • 问题内容: JAR文件和软件包之间有什么区别吗? 问题答案: 包是一种 逻辑上 组织您的类的方法。例如,您可以在每个源文件的顶部声明足够相关以一起驻留在包中。Java编译器和运行时还将期望您将此类文件放置在path中,该路径的根是类路径中的目录或JAR。 JAR文件使您可以 物理上 组织您的班级。您可以获取任何Java文件(以及它们的父目录,遵循上面讨论的目录结构),并将它们存储在JAR文件中。一

  • 问题内容: 我一直在阅读iBooks中的快速编程指南。有人可以向我解释函数和闭包之间的区别是什么。只是它没有名称并且可以在表达式中使用? 问题答案: 函数实际上只是命名为闭包。以下至少在概念上是等效的: 在使用声明方法的情况下,这变得有些复杂,例如,关于自动插入公共命名参数等,添加了一些有趣的糖,例如,变为`func myMethod(foo:Int, #bar:Int, 但是,即使方法只是闭包的

  • 问题内容: 在此示例中: 无法编译为: 而被编译器接受。 这个答案说明唯一的区别是,与不同,它允许您稍后引用类型,似乎并非如此。 是什么区别,并在这种情况下,为什么不第一编译? 问题答案: 通过使用以下签名定义方法: 并像这样调用它: 在jls§8.1.2中,我们发现(有趣的部分被我加粗了): 通用类声明定义了一组参数化类型(第4.5节), 每种可能通过类型arguments调用类型参数节的类型

  • 问题内容: Python模块和Python包之间有什么区别? 问题答案: 模块是单个文件(一个或多个文件),可在一个导入下导入并使用。例如 包是目录中提供包层次结构的模块的集合。

  • 问题内容: 我是AngularJS的新手。谁能解释一下这些AngularJS运算符之间的区别:用适当的示例隔离范围时。 问题答案: 允许将在指令属性上定义的值传递到指令的隔离范围。该值可以是简单的字符串值(),也可以是带有嵌入式表达式()的AngularJS插值字符串。可以将其视为从父作用域到子指令的“单向”通信。约翰·林德奎斯特(John Lindquist)进行了一系列简短的电视广播,解释了每

  • 问题内容: 我是AngularJS的新手。谁能解释一下这些AngularJS运算符之间的区别:用适当的示例隔离范围时。 问题答案: 允许将在指令属性上定义的值传递到指令的隔离范围。该值可以是简单的字符串值(),也可以是带有嵌入式表达式()的AngularJS插值字符串。将其视为从父作用域到子指令的“单向”通信。 允许指令的隔离范围将值传递到父范围中,以便在属性中定义的表达式中进行求值。请注意,指令

  • 我看了Facebook的留档(React.Component),它提到了如何在客户端/服务器上调用,而仅在客户端上调用。对服务器做什么?