为什么在Python 2.7和Python 3.4中创建类会有性能差异?

9
from timeit import Timer as T

def calc(n):
    return T("class CLS(object): pass").timeit(n)

print(calc(90000))
print(calc(90000))
print(calc(90000))

# python3.4
1.1714721370008192
1.0723806529986177
1.111804607000522

# python2.7
15.7533519268
16.7191421986
16.8397979736

为什么使用不同版本的Python创建类时会有如此大的时间差异呢?在同一台机器上进行了测试:

  • i5-3450 CPU @ 3.10GHz
  • 8gb ram

1
相关内容:https://dev59.com/H2kw5IYBdhLWcg3wMHoY - jonrsharpe
1
我想象对象与此有关。 - Padraic Cunningham
4
我已经缩小问题范围,发现是timeit调用了gc.disable()导致的。因为类总是创建很多循环引用,禁用收集器意味着每个CLS实例都不能被释放。你可以通过调用gc.get_objects()来获取被跟踪的对象列表。如果不禁用GC而只是将每个CLS实例附加到一个列表中以保持引用,则性能相似。谜题似乎在于为什么对Python 2来说,在这种情况下_PyObject_GC_Malloc的性能要差得多。 - Eryk Sun
使用您的代码片段,在 Python 2.7.3 中我得到了10.4693160057,在Python 3.2.3中我得到了10.087862968444824。通过去掉“object”,我在Python 2.7.3中得到了0.0276899337769。因此,“object”不太可能与此有任何关系。在Python 3.2和Python 3.4之间肯定存在性能改进,这导致了差异。而且似乎在Python版本之间类创建时间有很多小差异,所以可能无法将其缩小到单个更改。 - Michael Younkin
你可能想要查看 dis 模块,以了解操作上的差异。 - munk
2个回答

2

timeit禁用了垃圾收集器,否则会破坏保持类对象存活的循环引用。因此,在timeit完成之前,没有任何一个类被释放。

object.__subclasses__()通过内部弱引用集合引用这些类。旧的基于列表的tp_subclasses实现每次都要搜索整个列表以查找可以替换的死引用。随着每个子类的增加,这个过程需要更多的时间。另一方面,3.4中的新基于字典的设计可以在常数时间内添加引用。请参见issue 17936


感谢@MichaelYounkin指出了这在3.2中也很慢的问题。最初我试图将性能差异缩小到2.x和3.x之间的小对象分配器的变化,但在阅读他的评论后,我发现即使是3.3也比3.4慢得多。因此,我扫描了typeobject.c文件日志以查看最近的更改。


0

嗯,问题似乎出在Python 2.7中的旧式类和新式类之间。

在Python 3.4中,您可以看到使用对象和不使用对象之间的区别仅在于符号的加载(并不那么重要):

C:\TEMP>C:\Python34\python.exe
Python 3.4.2 (v3.4.2:ab2c023a9432, Oct  6 2014, 22:15:05) [MSC v.1600 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> def a():
...   class A(object): pass
...
>>> def b():
...   class B(): pass
...
>>> import dis
>>> dis.dis(a)
  2           0 LOAD_BUILD_CLASS
              1 LOAD_CONST               1 (<code object A at 0x020B8F20, file "<stdin>", line 2>)
              4 LOAD_CONST               2 ('A')
              7 MAKE_FUNCTION            0
             10 LOAD_CONST               2 ('A')
             13 LOAD_GLOBAL              0 (object)   # Extra step, not that expensive.
             16 CALL_FUNCTION            3 (3 positional, 0 keyword pair)
             19 STORE_FAST               0 (A)
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE
>>> dis.dis(b)
  2           0 LOAD_BUILD_CLASS
              1 LOAD_CONST               1 (<code object B at 0x020B8D40, file "<stdin>", line 2>)
              4 LOAD_CONST               2 ('B')
              7 MAKE_FUNCTION            0
             10 LOAD_CONST               2 ('B')
             13 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             16 STORE_FAST               0 (B)
             19 LOAD_CONST               0 (None)
             22 RETURN_VALUE
>>>

在Python 2.7中,您还需要涉及LOAD_TUPLE这一步骤:

C:\Users\jsargiot\Downloads\so>C:\Python27\python.exe
Python 2.7.8 (default, Jun 30 2014, 16:03:49) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> def a():
...   class A(object): pass
...
>>> def b():
...   class B(): pass
...
>>> import dis
>>> dis.dis(a)
  2           0 LOAD_CONST               1 ('A')
              3 LOAD_GLOBAL              0 (object)   # First extra step (just like 3.4)
              6 BUILD_TUPLE              1            # Second extra step, expensive
              9 LOAD_CONST               2 (<code object A at 01EAEA88, file "<stdin>", line 2>)
             12 MAKE_FUNCTION            0
             15 CALL_FUNCTION            0
             18 BUILD_CLASS
             19 STORE_FAST               0 (A)
             22 LOAD_CONST               0 (None)
             25 RETURN_VALUE
>>> dis.dis(b)
  2           0 LOAD_CONST               1 ('B')
              3 LOAD_CONST               3 (())
              6 LOAD_CONST               2 (<code object B at 01EB8EC0, file "<stdin>", line 2>)
              9 MAKE_FUNCTION            0
             12 CALL_FUNCTION            0
             15 BUILD_CLASS
             16 STORE_FAST               0 (B)
             19 LOAD_CONST               0 (None)
             22 RETURN_VALUE
>>>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接