为什么在Python中创建一个类比实例化一个类要慢得多?

Pro*_*ton 28 python performance memory-management class

我发现类的创建比实例化类慢.

>>> from timeit import Timer as T
>>> def calc(n):
...     return T("class Haha(object): pass").timeit(n)

<<After several these 'calc' things, at least one of them have a big number, eg. 100000>>

>>> calc(9000)
15.947055101394653
>>> calc(9000)
17.39099097251892
>>> calc(9000)
18.824054956436157
>>> calc(9000)
20.33335590362549
Run Code Online (Sandbox Code Playgroud)

是的,创建9000个类需要16秒,并且在后续调用中变得更慢.

还有这个:

>>> T("type('Haha', b, d)", "b = (object, ); d = {}").timeit(9000)
Run Code Online (Sandbox Code Playgroud)

给出类似的结果.

但实例化不会受到影响:

>>> T("Haha()", "class Haha(object): pass").timeit(5000000)
0.8786070346832275
Run Code Online (Sandbox Code Playgroud)

在不到一秒的时间内完成5000000个实例.

是什么让这个创作变得昂贵?

为什么创作过程会变慢?

编辑:

如何重现:

开始一个新的python进程,最初的几个"calc(10000)"在我的机器上给出了0.5的数字.并尝试一些更大的值,calc(100000),它甚至不能以10秒结束,中断它,并且计算(10000),给出15秒.

编辑:

其他事实:

如果你在'calc'变慢后gc.collect(),你可以在开始时获得'正常'的速度,但是后续调用的时间会增加

>>> from a import calc
>>> calc(10000)
0.4673938751220703
>>> calc(10000)
0.4300072193145752
>>> calc(10000)
0.4270968437194824
>>> calc(10000)
0.42754602432250977
>>> calc(10000)
0.4344758987426758
>>> calc(100000)
^CTraceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "a.py", line 3, in calc
    return T("class Haha(object): pass").timeit(n)
  File "/usr/lib/python2.7/timeit.py", line 194, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
KeyboardInterrupt
>>> import gc
>>> gc.collect()
234204
>>> calc(10000)
0.4237039089202881
>>> calc(10000)
1.5998330116271973
>>> calc(10000)
4.136359930038452
>>> calc(10000)
6.625348806381226
Run Code Online (Sandbox Code Playgroud)

Rom*_*huk 32

这可能会给你直觉:

>>> class Haha(object): pass
...
>>> sys.getsizeof(Haha)
904
>>> sys.getsizeof(Haha())
64
Run Code Online (Sandbox Code Playgroud)

类对象是比类的实例更加复杂和昂贵的结构.

  • Veedrac的答案应该被接受.(这个晚你能改变一个接受的答案吗?) (2认同)

Vee*_*rac 24

Ahahaha!疑难杂症!

是否在没有此补丁的Python版本上完成了此操作?(提示:它是)

如果需要校样,请检查行号.

Marcin是对的:当结果看起来很糟糕时,你可能有一个棘手的基准.运行gc.disable(),结果自己重现.它只是表明,当你禁用垃圾收集时,你会得到垃圾结果!


更清楚的是,运行长基准测试的原因是:

  • timeit 禁用垃圾收集,因此过大的基准测试需要更长时间(指数级)

  • timeit 没有恢复异常的垃圾收集

  • 您使用异步异常退出长时间运行的进程,关闭垃圾回收


sou*_*eck 10

快速执行以下功能:

def a():
    class Haha(object):
         pass



def b():
    Haha()
Run Code Online (Sandbox Code Playgroud)

得到:

2           0 LOAD_CONST               1 ('Haha')
            3 LOAD_GLOBAL              0 (object)
            6 BUILD_TUPLE              1
            9 LOAD_CONST               2 (<code object Haha at 0x7ff3e468bab0, file "<stdin>", line 2>)
            12 MAKE_FUNCTION            0
            15 CALL_FUNCTION            0
            18 BUILD_CLASS         
            19 STORE_FAST               0 (Haha)
            22 LOAD_CONST               0 (None)
            25 RETURN_VALUE        
Run Code Online (Sandbox Code Playgroud)

2           0 LOAD_GLOBAL              0 (Haha)
            3 CALL_FUNCTION            0
            6 POP_TOP             
            7 LOAD_CONST               0 (None)
            10 RETURN_VALUE        
Run Code Online (Sandbox Code Playgroud)

因此.

从它的外观来看,它只是在创建一个类时做了更多的事情.它必须初始化类,将其添加到dicts,以及其他任何地方,而在Haha()只是调用函数的情况下.

正如你所注意到的那样,当速度太慢时再进行垃圾收集,所以Marcin说它可能是内存碎片问题.