¿Qué es más eficiente en Python en términos de uso de memoria y consumo de CPU: diccionario u objeto?
Antecedentes: tengo que cargar una gran cantidad de datos en Python. Creé un objeto que es solo un contenedor de campo. Crear instancias de 4M y ponerlas en un diccionario tomó aproximadamente 10 minutos y ~ 6GB de memoria. Una vez que el diccionario está listo, acceder a él es un abrir y cerrar de ojos.
Ejemplo: para verificar el rendimiento, escribí dos programas simples que hacen lo mismo: uno está usando objetos, otro diccionario:
Objeto (tiempo de ejecución ~ 18 segundos):
class Obj(object):
def __init__(self, i):
self.i = i
self.l = []
all = {}
for i in range(1000000):
all[i] = Obj(i)
Diccionario (tiempo de ejecución ~ 12 segundos):
all = {}
for i in range(1000000):
o = {}
o['i'] = i
o['l'] = []
all[i] = o
Pregunta: ¿Estoy haciendo algo mal o el diccionario es más rápido que el objeto? Si el diccionario funciona mejor, ¿alguien puede explicar por qué?
fuente
Respuestas:
¿Has intentado usar
__slots__
?De la documentación :
Entonces, ¿esto ahorra tiempo y memoria?
Comparando los tres enfoques en mi computadora:
test_slots.py:
test_obj.py:
test_dict.py:
test_namedtuple.py (compatible con 2.6):
Ejecutar benchmark (usando CPython 2.5):
Usando CPython 2.6.2, incluida la prueba de tupla nombrada:
Entonces sí (no es realmente una sorpresa), el uso
__slots__
es una optimización del rendimiento. El uso de una tupla con nombre tiene un rendimiento similar a__slots__
.fuente
El acceso a atributos en un objeto usa el acceso al diccionario detrás de escena, por lo que al usar el acceso a atributos está agregando una sobrecarga adicional. Además, en el caso del objeto, está incurriendo en una sobrecarga adicional debido, por ejemplo, a asignaciones de memoria adicionales y ejecución de código (por ejemplo, del
__init__
método).En su código, si
o
es unaObj
instancia,o.attr
es equivalente ao.__dict__['attr']
una pequeña cantidad de sobrecarga adicional.fuente
o.__dict__["attr"]
es el que tiene sobrecarga adicional, tomando un bytecode adicional op; obj.attr es más rápido. (Por supuesto, el acceso a los atributos no va a ser más lento que el acceso a la suscripción; es una ruta de código crítica y muy optimizada)¿Has considerado usar una tupla con nombre ? ( enlace para python 2.4 / 2.5 )
Es la nueva forma estándar de representar datos estructurados que le brinda el rendimiento de una tupla y la conveniencia de una clase.
Su único inconveniente en comparación con los diccionarios es que (como las tuplas) no le da la capacidad de cambiar los atributos después de la creación.
fuente
Aquí hay una copia de la respuesta @hughdbrown para python 3.6.1, hice el conteo 5 veces más grande y agregué un código para probar la huella de memoria del proceso de python al final de cada ejecución.
Antes de que los votantes voten, tenga en cuenta que este método de contar el tamaño de los objetos no es exacto.
Y estos son mis resultados.
Mi conclusión es:
fuente
Resultados:
fuente
No hay pregunta.
Tiene datos, sin otros atributos (sin métodos, nada). Por lo tanto, tiene un contenedor de datos (en este caso, un diccionario).
Por lo general, prefiero pensar en términos de modelado de datos . Si hay un gran problema de rendimiento, entonces puedo renunciar a algo en la abstracción, pero solo con muy buenas razones.
La programación tiene que ver con la gestión de la complejidad, y el mantenimiento de la abstracción correcta es a menudo una de las formas más útiles para lograr dicho resultado.
Sobre las razones por las que un objeto es más lento, creo que su medición no es correcta.
Está realizando asignaciones muy pequeñas dentro del ciclo for, y por lo tanto, lo que ve allí es el tiempo diferente necesario para instanciar un dict (objeto intrínseco) y un objeto "personalizado". Aunque desde la perspectiva del lenguaje son iguales, tienen una implementación bastante diferente.
Después de eso, el tiempo de asignación debería ser casi el mismo para ambos, ya que al final los miembros se mantienen dentro de un diccionario.
fuente
Existe otra forma de reducir el uso de memoria si se supone que la estructura de datos no contiene ciclos de referencia.
Comparemos dos clases:
y
Se hizo posible ya que las
structclass
clases basadas no admiten la recolección de basura cíclica, lo que no es necesario en tales casos.También hay una ventaja sobre la
__slots__
clase basada en la base: puede agregar atributos adicionales:fuente
Aquí están mis ejecuciones de prueba del muy buen script de @ Jarrod-Chesney. A modo de comparación, también lo ejecuto contra python2 con "rango" reemplazado por "xrange".
Por curiosidad, también agregué pruebas similares con OrderedDict (ordict) para comparar.
Python 3.6.9:
Python 2.7.15+:
Entonces, en ambas versiones principales, las conclusiones de @ Jarrod-Chesney todavía se ven bien.
fuente