Qu'est-ce qui est plus efficace en Python en termes d'utilisation de la mémoire et de consommation CPU - Dictionnaire ou Objet?
Contexte: je dois charger une énorme quantité de données dans Python. J'ai créé un objet qui n'est qu'un conteneur de champ. Créer des instances 4M et les mettre dans un dictionnaire a pris environ 10 minutes et ~ 6 Go de mémoire. Une fois que le dictionnaire est prêt, y accéder est un clin d'œil.
Exemple: pour vérifier les performances, j'ai écrit deux programmes simples qui font la même chose - l'un utilise des objets, l'autre un dictionnaire:
Objet (temps d'exécution ~ 18sec):
class Obj(object):
def __init__(self, i):
self.i = i
self.l = []
all = {}
for i in range(1000000):
all[i] = Obj(i)
Dictionnaire (temps d'exécution ~ 12sec):
all = {}
for i in range(1000000):
o = {}
o['i'] = i
o['l'] = []
all[i] = o
Question: Est-ce que je fais quelque chose de mal ou le dictionnaire est-il juste plus rapide qu'un objet? Si effectivement le dictionnaire fonctionne mieux, quelqu'un peut-il expliquer pourquoi?
la source
Réponses:
Avez-vous essayé d'utiliser
__slots__
?De la documentation :
Est-ce que cela économise du temps et de la mémoire?
Comparaison des trois approches sur mon ordinateur:
test_slots.py:
test_obj.py:
test_dict.py:
test_namedtuple.py (pris en charge dans 2.6):
Exécutez le benchmark (en utilisant CPython 2.5):
Utilisation de CPython 2.6.2, y compris le test de tuple nommé:
Alors oui (pas vraiment une surprise), l'utilisation
__slots__
est une optimisation des performances. L'utilisation d'un tuple nommé a des performances similaires à celles de__slots__
.la source
L'accès aux attributs dans un objet utilise l'accès au dictionnaire en arrière-plan. Ainsi, en utilisant l'accès aux attributs, vous ajoutez une surcharge supplémentaire. De plus, dans le cas de l'objet, vous subissez une surcharge supplémentaire en raison, par exemple, d'allocations de mémoire supplémentaires et de l'exécution de code (par exemple de la
__init__
méthode).Dans votre code, si
o
est uneObj
instance, celao.attr
équivaut ào.__dict__['attr']
une petite quantité de surcharge supplémentaire.la source
o.__dict__["attr"]
est celui avec des frais généraux supplémentaires, prenant une opération de bytecode supplémentaire; obj.attr est plus rapide. (Bien sûr, l'accès aux attributs ne sera pas plus lent que l'accès aux abonnements - c'est un chemin de code critique et fortement optimisé.)Avez-vous envisagé d'utiliser un namedtuple ? ( lien pour python 2.4 / 2.5 )
C'est la nouvelle façon standard de représenter des données structurées qui vous donne les performances d'un tuple et la commodité d'une classe.
Le seul inconvénient par rapport aux dictionnaires est que (comme les tuples) cela ne vous donne pas la possibilité de changer les attributs après la création.
la source
Voici une copie de la réponse @hughdbrown pour python 3.6.1, j'ai agrandi le décompte 5x et ajouté du code pour tester l'empreinte mémoire du processus python à la fin de chaque exécution.
Avant que les downvoters ne s'y attardent, sachez que cette méthode de comptage de la taille des objets n'est pas précise.
Et ce sont mes résultats
Ma conclusion est:
la source
Résultats:
la source
Il n'y a aucune question.
Vous avez des données, sans autres attributs (pas de méthodes, rien). Vous avez donc un conteneur de données (dans ce cas, un dictionnaire).
Je préfère généralement penser en termes de modélisation de données . S'il y a un énorme problème de performance, je peux abandonner quelque chose dans l'abstraction, mais seulement pour de très bonnes raisons.
La programmation consiste à gérer la complexité, et le maintien de l' abstraction correcte est très souvent l'un des moyens les plus utiles pour obtenir un tel résultat.
À propos des raisons pour lesquelles un objet est plus lent, je pense que votre mesure n'est pas correcte.
Vous effectuez trop peu d'affectations à l'intérieur de la boucle for, et donc ce que vous voyez là est le temps différent nécessaire pour instancier un dict (objet intrinsèque) et un objet "personnalisé". Bien que du point de vue linguistique, ils soient identiques, ils ont une implémentation assez différente.
Après cela, le temps d'affectation devrait être presque le même pour les deux, car à la fin, les membres sont conservés dans un dictionnaire.
la source
Il existe encore un autre moyen de réduire l'utilisation de la mémoire si la structure de données n'est pas censée contenir des cycles de référence.
Comparons deux classes:
et
Cela est devenu possible car les
structclass
classes basées sur la base de données ne prennent pas en charge le garbage collection cyclique, ce qui n'est pas nécessaire dans de tels cas.Il y a aussi un avantage sur la
__slots__
classe surbasée: vous pouvez ajouter des attributs supplémentaires:la source
Voici mes tests du très joli script de @ Jarrod-Chesney. A titre de comparaison, je l'exécute également sur python2 avec "range" remplacé par "xrange".
Par curiosité, j'ai également ajouté des tests similaires avec OrderedDict (ordict) pour comparaison.
Python 3.6.9:
Python 2.7.15+:
Ainsi, sur les deux versions majeures, les conclusions de @ Jarrod-Chesney semblent toujours bonnes.
la source