jjzjj

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

coder 2023-08-26 原文

在 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/

    有关python - cpdef 和封装在 def 中的 cdef 有什么区别?的更多相关文章

    1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

      总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

    2. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    3. ruby - 其他文件中的 Rake 任务 - 2

      我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

    4. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

      作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

    5. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    6. ruby-on-rails - Rails 3 中的多个路由文件 - 2

      Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

    7. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

      我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

    8. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

      我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

    9. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

      我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

    10. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

      我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

    随机推荐