Por que há a diferença entre a criação de classes no python 2.7 e o desempenho do 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

Por que há tanta diferença no tempo de criação de classes usando diferentes versões do python? Testado na mesma máquina:

  • CPU i5-3450 a 3.10GHz
  • 8 gb de ram
por Igor 03.11.2014 в 12:30
fonte

2 respostas

2

timeit desativa o coletor de lixo, que, de outra forma, quebraria os ciclos que mantêm um objeto de classe ativo. Portanto, nenhuma das classes é desalocada até que timeit tenha terminado.

object.__subclasses__() faz referência a essas classes por meio de uma coleção interna de referências fracas. A antiga implementação baseada em lista de tp_subclasses pesquisa a lista inteira a cada vez para localizar uma referência morta que pode ser substituída. Esse processo leva mais tempo com cada subclasse adicional. Por outro lado, o novo design baseado em dict em 3.4 pode adicionar uma referência em tempo constante. Veja a edição 17936 .

Graças a @ MichaelYounkin por apontar como isso também é lento em 3.2. Inicialmente eu tentei diminuir a diferença de desempenho para uma mudança no alocador de objetos pequenos entre 2.xe 3.x, mas depois de ler seu comentário descobri que mesmo 3.3 era consideravelmente mais lento que 3.4. Então eu fiz a varredura do typeobject.c filelog para revisar as mudanças recentes.

    
por eryksun 10.11.2014 / 10:36
fonte
0

Bem, o problema parece ser com as classes de estilo antigo versus estilo novo no python 2.7.

No python 3.4 você pode ver que a diferença entre usar o objeto e não usá-lo é apenas o carregamento do símbolo (não tão significativo):

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

Enquanto no Python 2.7 você tem outro passo que envolve 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
>>>
    
por Joaquin Sargiotto 08.11.2014 / 21:33
fonte