创建一个类实例需要多少次函数调用



知道在python中调用函数是昂贵的,这个问题的答案对优化决策有一定的影响,例如将直接的单函数数值方法与面向对象方法进行比较。所以我想了解

  • 所需的函数调用的典型数量是多少
  • 所需的最小函数调用次数是多少
  • 是什么增加了通话次数
  • 用户创建的类与内置类相比如何
  • 对象删除(包括垃圾收集)怎么样

我的谷歌傅无法找到这个问题的答案。

编辑:因此,为了总结评论并防止更多票数接近,以下是一些澄清:

  • 与调用普通python函数相比,我对python实例创建的时间复杂性感兴趣
  • 出于这个问题的目的,让我们仅限于最新的CPython版本

请参阅Eli Bendersky的Python对象创建。

详细引用结论:

为了避免我们因树木而失去森林,让我们重新审视这个问题文章开头是。CPython执行j = Joe()时会发生什么?

  • 由于Joe没有显式元类,因此type是它的类型。因此,typetp_call时隙,即type_call,被调用
  • type_call通过调用Joe的tp_new时隙开始:
    • 由于Joe没有显式的基类,因此它的基类为object。因此,object_new被调用
    • 由于Joe是Python定义的类,因此它没有自定义的tp_alloc槽。因此,object_new调用PyType_GenericAlloc
    • PyType_GenericAlloc分配并初始化一块足够大的内存,以包含Joe
  • 然后CCD_ 15继续并且对新创建的对象调用CCD_。
    • 由于Joe没有定义__init__,因此它的基的__init__被调用,即object_init
    • object_init什么也不做
  • 新对象从type_call返回,并绑定到名称j

我已经按照注释中的建议进行了操作,并在以下测试用例中使用了timeit

def a():
    pass
class A(object):
    pass
class B(object):
    def __init__(self):
        pass
class NOPType(type):
    pass
class C(object):
    __metaclass__ = NOPType
    def __init__(self):
        pass
class D(object):
    def __new__(cls, *args, **kwargs):
        return super(D, cls).__new__(cls)
    def __init__(self):
        pass
class E(A):
    def __init__(self):
        super(E, self).__init__()

测试结果:

$python-m timeit-s"导入tst"tst.a()"10000000个循环,3个最佳:每个循环0.149 usec$python-m timeit-s"导入tst"tst.A()"10000000个循环,3个最佳:每个循环0.169 usec$python-m timeit-s"导入tst"tst.B()"1000000个循环,3个最佳:每个循环0.384 usec$python-m timeit-s"导入tst"tst.C()"1000000个循环,3个最佳:每个循环0.397 usec$python-m timeit-s"导入tst"tst.D()"1000000个循环,3个最佳:每个循环1.09 usec$python-m timeit-s"导入tst"tst.E()"1000000个循环,3个最佳:每个循环0.827 usec

使用函数调用作为基线,结果如下:

  • 一个基本的实例化需要1.1倍的时间
  • 添加__init__方法会将因子增加到2.6
  • 添加一个no-op元类只是稍微贵一点,价格是2.7
  • 它相当于7.3个函数调用,而不是添加一个基本的__new__
  • 一个只有一个子类的类相当于5.6个函数调用

对于最后两个结果,如果将对super的调用替换为其返回值,则可以减去大约2。

在CPython 2.7中,这应该可以粗略估计花费时间的python类与python函数的比较。

最新更新