gpt4 book ai didi

python - cpdef 和封装在 def 中的 cdef 有什么区别?

转载 作者:太空狗 更新时间:2023-10-29 22:14:42 25 4
gpt4 key购买 nike

在 Cython 文档中有一个 example他们给出了两种编写 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 代码几乎相同。
  • 对于绑定(bind)方法,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 - 前缀 pf ),它不会复制代码并在途中的某个地方调用快速函数。
  • 创建了一个 python 包装器,名为 __pyx_pw_3foo_3do_nothing_cp (前缀 pw)
  • 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()发生以下情况:
  • 绑定(bind)到名称的函数指针 do_nothing_cp找到了,在我们的例子中是 python-wrapper pw -功能。
  • pw -function 通过函数指针调用,并调用 pf -function(作为 C 功能)
  • pf -函数调用快速f -功能。

  • 如果我们拨打 do_nothing_cp 会发生什么在 cython 模块内?
    def call_do_nothing_cp():
    do_nothing_cp()

    显然,在这种情况下,cython 不需要 python 机制来定位函数——它可以直接使用快速 f -function 通过 c 函数调用,绕过 pwpf职能。

    如果我们包装 cdef 会发生什么 def 中的函数-功能?
    cdef _do_nothing():
    pass

    def do_nothing():
    _do_nothing()

    Cython 执行以下操作:
  • 快速_do_nothing -function 被创建,对应于f - 以上功能。
  • pf -功能为 do_nothing创建,调用 _do_nothing在路上的某个地方。
  • 一个 python 包装器,即 pw创建了包装 pf 的函数-功能
  • 该功能绑定(bind)到 foo.do_nothing通过函数指针指向 python 包装器 pw -功能。

  • 如您所见 - 与 cpdef 没有太大区别-方法。
    cdef -functions 只是简单的 c-function,但是 defcpdef函数是第一类的python函数 - 你可以这样做:
    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 版本 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 version 稍微快一点,但无论如何与 python 函数调用的开销相比,差别不大。

    类方法:

    由于可能存在多态性,类方法的情况稍微复杂一些。让我们开始:
    cdef class A:
    cpdef do_nothing_cp(self):
    pass

    乍一看,和上面的情况没有太大区别:
  • 快速,仅限 c,f发出函数的 -prefix-version
  • 发出一个 python(前缀 pf)版本,它调用 f -功能
  • 一个 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, /*tp_methods*/
    ...
    };

    显然,绑定(bind)版本必须具有隐式参数 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() 时会发生什么?
  • `B-pw-call_do_nothing' 被定位和调用。
  • 它叫 B-pf-call_do_nothing ,
  • 其中调用 B-f-call_do_nothing ,
  • 其中调用 A-f-do_nothing_cp ,绕过 pwpf -版本。

  • 当我们添加以下类时会发生什么 C ,覆盖 do_nothing_cp -功能?
    import foo
    def class C(foo.B):
    def do_nothing_cp(self):
    print("I do something!")

    现在拨打 C().call_do_nothing()造成:
  • call_do_nothing' of the C -class being located and called which means, B 的 pw-call_do_nothing' -class 被定位和调用,
  • 其中调用 B-pf-call_do_nothing ,
  • 其中调用 B-f-call_do_nothing ,
  • 其中调用 A-f-do_nothing (正如我们已经知道的!),绕过 pwpf -版本。

  • 现在在 4. 步骤中,我们需要在 A-f-do_nothing() 中调度调用为了得到正确的 C.do_nothing()称呼!幸运的是,我们手头的函数中有这个调度!

    更复杂的是:如果类 C 会怎样?也是 cdef -类(class)?发送通过 __dict__不会工作,因为 cdef 类没有 __dict__ ?

    对于 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。

    至于性能,比较 cpdefcdef + def我得到:
                              cpdef         def+cdef
    A.do_nothing 107ns 108ns
    B.call_nothing 109ns 116ns

    所以如果有人的话,差别不大, cpdef稍微快一点。

    关于python - cpdef 和封装在 def 中的 cdef 有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48864631/

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