28

我发现创建一个类比实例化一个类要慢得多。

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

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

还有这个:

>>> T("type('Haha', b, d)", "b = (object, ); d = {}").timeit(9000)

给出类似的结果。

但实例化不会受到影响:

>>> T("Haha()", "class Haha(object): pass").timeit(5000000)
0.8786070346832275

在不到一秒的时间内 5000000 个实例。

是什么让创作如此昂贵?

为什么创建过程会变慢?

编辑:

如何重现:

开始一个新的 python 进程,最初的几个“calc(10000)”在我的机器上给出了 0.5 的数字。尝试一些更大的值,calc(100000),它甚至不能在 10 秒内结束,中断它,然后 calc(10000),给出 15 秒。

编辑:

附加事实:

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

>>> 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
4

4 回答 4

32

这可能会给你直觉:

>>> class Haha(object): pass
...
>>> sys.getsizeof(Haha)
904
>>> sys.getsizeof(Haha())
64

类对象该类的实例更复杂和昂贵的结构。

于 2012-04-09T11:37:14.663 回答
24

啊哈哈哈!明白了!

这可能是在没有这个补丁的 Python 版本上完成的吗?(提示:它是

如果需要证明,请检查行号。

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


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

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

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

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

于 2014-09-04T05:39:39.513 回答
10

以下功能的快速分解:

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



def b():
    Haha()

给出:

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        

2           0 LOAD_GLOBAL              0 (Haha)
            3 CALL_FUNCTION            0
            6 POP_TOP             
            7 LOAD_CONST               0 (None)
            10 RETURN_VALUE        

因此。

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

正如您注意到当垃圾收集速度太慢时再次加速垃圾收集,所以 Marcin 说这可能是内存碎片问题是正确的。

于 2012-04-09T11:40:17.113 回答
3

它不是:只有你人为的测试显示类创建缓慢。事实上,正如@Veedrac 在他的回答中所显示的那样,这个结果是 timeit 禁用垃圾收集的产物。

Downvoters:给我看一个非人为的例子,其中类创建很慢。

在任何情况下,您的时间都会受到当时系统负载的影响。它们实际上只对几乎同时进行的比较有用。我为 9000 个班级创作获得了大约 0.5 秒的时间。事实上,ideone 大约需要 0.3 秒,即使重复执行:http: //ideone.com/Du859。甚至没有上升趋势。

因此,总而言之,它在您的计算机上比其他计算机慢得多,并且在其他计算机上重复测试没有上升趋势(根据您最初的说法)。测试大量实例化确实显示速度变慢,大概是因为该过程消耗了大量内存。您已经证明分配大量内存会减慢进程。做得好。

完整的ideone代码:

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

for i in xrange(30):
print calc(9000)
于 2012-04-09T11:36:16.537 回答