¿Por qué el acceso a las variables locales es más rápido que el acceso de los miembros de la clase en Python?

Mientras trataba de abordar un problema más complejo, comparé la velocidad de acceso con las variables locales frente a las variables miembro.

Aquí un progtwig de prueba:

#!/usr/bin/env python MAX=40000000 class StressTestMember(object): def __init__(self): self.m = 0 def do_work(self): self.m += 1 self.m *= 2 class StressTestLocal(object): def __init__(self): pass def do_work(self): m = 0 m += 1 m *= 2 # LOCAL access test for i in range(MAX): StressTestLocal().do_work() # MEMBER access test for i in range(MAX): StressTestMember().do_work() 

Sé que podría parecer una mala idea crear una instancia de StressTestMember y StressTestLocal en cada iteración, pero tiene sentido en el progtwig modelado donde estos son básicamente registros activos.

Después de un simple punto de referencia,

  • Prueba de acceso LOCAL: 0m22.836
  • Prueba de acceso MIEMBRO: 0m32.648s

La versión local es ~ 33% más rápida mientras sigue siendo parte de una clase. ¿Por qué?

self.m += 1 significa que tiene que buscar una variable local llamada self y luego encontrar el atributo llamado m

Por supuesto, si solo tiene que buscar una variable local, será más rápido sin el paso adicional.

Puede ser útil observar lo que sucede debajo del capó:

 >>> import dis >>> dis.dis(StressTestLocal.do_work) 18 0 LOAD_CONST 1 (0) 3 STORE_FAST 1 (m) 19 6 LOAD_FAST 1 (m) 9 LOAD_CONST 2 (1) 12 INPLACE_ADD 13 STORE_FAST 1 (m) 20 16 LOAD_FAST 1 (m) 19 LOAD_CONST 3 (2) 22 INPLACE_MULTIPLY 23 STORE_FAST 1 (m) 26 LOAD_CONST 0 (None) 29 RETURN_VALUE >>> dis.dis(StressTestMember.do_work) 10 0 LOAD_FAST 0 (self) 3 DUP_TOP 4 LOAD_ATTR 0 (m) 7 LOAD_CONST 1 (1) 10 INPLACE_ADD 11 ROT_TWO 12 STORE_ATTR 0 (m) 11 15 LOAD_FAST 0 (self) 18 DUP_TOP 19 LOAD_ATTR 0 (m) 22 LOAD_CONST 2 (2) 25 INPLACE_MULTIPLY 26 ROT_TWO 27 STORE_ATTR 0 (m) 30 LOAD_CONST 0 (None) 33 RETURN_VALUE 

Los nombres locales son más rápidos porque Python hace una optimización de que los nombres locales no necesitan acceso a dictado, por otro lado, los atributos de instancia necesitan acceder al __dict__ del objeto.

Esta es también la razón por la que los nombres locales son más rápidos que los nombres globales.