2017-07-21 5 views
5

Celui-ci m'a complètement déconcerté.Python OrderDict sputtering par rapport à dict()

asset_hist = [] 
for key_host, val_hist_list in am_output.asset_history.items(): 
    for index, hist_item in enumerate(val_hist_list): 
     #row = collections.OrderedDict([("computer_name", key_host), ("id", index), ("hist_item", hist_item)]) 
     row = {"computer_name": key_host, "id": index, "hist_item": hist_item} 
     asset_hist.append(row) 

Ce code fonctionne parfaitement avec la ligne de collections commentée. Cependant, lorsque je commente la ligne row = dict et supprime le commentaire de la collection, les choses deviennent très étranges. Environ 4 millions de ces lignes sont générées et ajoutées à asset_hist. Donc, quand j'utilise row = dict, toute la boucle se termine en environ 10 millisecondes, ce qui est rapide comme l'éclair. Lorsque j'utilise le dictionnaire ordonné, j'ai attendu plus de 10 minutes et il n'a toujours pas fini. Maintenant, je sais que OrderDict est censé être un peu plus lent qu'une dictée, mais il est supposé être environ 10x plus lent au pire et par mes calculs, il est environ 100 000 fois plus lent dans cette fonction.

J'ai décidé d'imprimer l'index dans la boucle la plus basse pour voir ce qui se passait. Assez intéressant, j'ai remarqué une pulvérisation dans la sortie de la console. L'index s'imprimerait très rapidement sur l'écran, puis s'arrêterait environ 3-5 secondes avant de continuer. Am_output.asset_history est un dictionnaire qui a une clé, un hôte, et chaque ligne est une liste de chaînes. Par exemple.

am_output.asset_history = { "host1": [ "string1", "string2", ...], "host2": [ "string1", "string2", ...], ...}

EDIT: Analyse avec pulvérisation cathodique OrderedDict

mémoire totale sur ce serveur VM: seulement 8 Go ... besoin pour obtenir plus provissioned.

LOOP NUM

184796 (~ 5 secondes d'attente, ~ 60% d'utilisation de la mémoire)

634481 (~ 5 secondes d'attente, ~ 65% d'utilisation de la mémoire)

1197564 (~ 5 secondes d'attente , ~ 70% d'utilisation de la mémoire)

1899247 (~ 5 secondes d'attente, ~ 75% d'utilisation de la mémoire)

2777296 (~ 5 secondes d'attente, ~ 80% d'utilisation de la mémoire)

3873730 (LONG WAIT ... 20 minutes et attendirent abandonnèrent !, 88,3% l'utilisation de la mémoire, le processus est toujours en cours)

Si l'attente se change à chaque course.

EDIT: Redémarrez-la, cette fois-ci, arrêtez-vous sur 3873333, à proximité de l'endroit où elle s'est arrêtée auparavant. Il s'est arrêté après avoir formé la rangée, en essayant d'ajouter ... Je n'ai pas remarqué cette dernière tentative mais elle était là aussi ... le problème est avec la ligne append, pas la ligne row ... Je suis toujours déconcerté. Voici la ligne produite juste avant le long arrêt (ajout de la ligne à l'instruction print) ... hostname modifié pour protéger l'innocent:

3873333: OrderedDict ([('computer_name', 'bg-fd5612ea'), ('id', 1), ('hist_item', "sys1 Normalizer (sys1-4): Le nom de domaine ne peut pas être déterminé à partir de sys1 Nom 'bg-fd5612ea'."))

+1

J'ai du mal à croire que ce que vous observez est dû au comportement 'dict' vs' OrderedDict', est-il possible que quelque chose d'autre change? Je pense aussi que quelqu'un aura du mal à reproduire cela –

+1

Ouais, ils auraient d'abord besoin de générer l'énorme dictionnaire, probablement des générateurs de chaînes aléatoires suffiraient. Mais je suis tout à fait sérieux, quand je déplace le # en bas d'une ligne cela arrive. Il faut aussi noter: quand il est bloqué dans cette boucle en utilisant un dict ordonné et que j'appuie sur Ctrl + C pour arrêter la boucle, c'est comme si rien ne se passait ... Je dois appuyer sur Ctrl + Z pour sortir. – gunslingor

+0

Quelle version de Python? Dans le plus récent, OrderedDict est simplement un alias pour dict, car l'implémentation actuelle est ordonnée par les détails de l'implémentation. – RemcoGerlich

Répondre

1

Comme le prouvent vos propres tests, vous manquez de mémoire. Même sur CPython 3.6 (où dict est réellement commandé, mais pas encore comme une garantie de langue), OrderedDict a un surdébit de mémoire significatif par rapport à dict; il est toujours implémenté avec une liste chaînée pour préserver l'ordre et supporter l'itération facile, en réordonnant avec move_to_end, etc. Vous pouvez dire juste en vérifiant avec sys.getsizeof (les résultats exacts différeront par la version de Python et construisent la largeur de bande, 32 contre 64 bits):

>>> od = OrderedDict([("a", 1), ("b", 2), ("c", 3)]) 
>>> d = {**od} 
>>> sys.getsizeof(od) 
464 # On 3.5 x64 it's 512 
>>> sys.getsizeof(d) 
240 # On 3.5 x64 it's 288 

Ignorer les données stockées, les frais généraux pour la OrderedDict est presque deux fois ici celle de la plaine dict. Si vous faites 4 millions de ces éléments, sur ma machine, cela ajouterait un overhead de plus de 850 Mo (sur les 3.5 et 3.6).

Il est probable que la combinaison de tous les autres programmes de votre système, plus votre programme Python, dépasse la quantité de RAM allouée à votre machine, et que vous êtes bloqué par le swap. En particulier, chaque fois que asset_hist doit être déployé pour de nouvelles entrées, il a probablement besoin de faire une recherche dans une grande partie de celui-ci (qui a été renvoyé pour cause d'inutilisation) et chaque fois qu'une opération de récupération de place cyclique se déclenche. et les désallocations par défaut), toutes les OrderedDict sont renvoyées pour vérifier si elles sont toujours référencées en dehors des cycles (vous pouvez vérifier si le fonctionnement du CPG est le problème principal en désactivant le GC cyclique via gc.disable()). Compte tenu de votre cas d'utilisation particulier, je recommande fortement d'éviter à la fois dict et OrderedDict cependant. La surcharge de même dict, même la forme la moins chère sur Python 3.6, est un peu extrême lorsque vous avez un ensemble de trois clés fixes à plusieurs reprises. Au lieu de cela, use collections.namedtuple, qui est conçu pour les objets légers référencables par leur nom ou index (ils agissent comme tuple s, mais permettent également d'accéder à chaque valeur comme un attribut nommé), ce qui réduit considérablement le coût de la mémoire de votre programme vers le haut même quand la mémoire n'est pas un problème).

Par exemple:

from collections import namedtuple 

ComputerInfo = namedtuple('ComputerInfo', ['computer_name', 'id', 'hist_item']) 

asset_hist = [] 
for key_host, val_hist_list in am_output.asset_history.items(): 
    for index, hist_item in enumerate(val_hist_list): 
     asset_hist.append(ComputerInfo(key_host, index, hist_item)) 

La seule différence est en cours d'utilisation que vous remplacez row['computer_name'] avec row.computer_name, ou si vous avez besoin de toutes les valeurs, vous pouvez décompresser comme un régulier tuple, par exemple comphost, idx, hist = row. Si vous avez besoin d'un vrai OrderedDict temporairement (ne les stockez pas pour tout), vous pouvez appeler row._asdict() pour obtenir un OrderedDict avec le même mappage que le namedtuple, mais ce n'est normalement pas nécessaire. Les économies de mémoire sont significatives; sur mon système, les trois éléments namedtuple réduisent le surcoût par élément à 72 octets, moins d'un tiers de celui de 3,6 dict et moins d'un sixième de 3,6 OrderedDict (et trois éléments namedtuple reste 72 octets sur 3,5, où dict/OrderedDict sont plus grandes pré-3.6). Cela peut sauver encore plus que cela; tuple s (et namedtuple par extension) sont alloués comme un seul contigu C struct, alors que dict et compagnie sont au moins deux allocations (une pour la structure de l'objet, une ou plusieurs pour les parties dynamiquement redimensionnables de la structure), chacune pouvant payer les frais généraux d'allocation et les coûts d'alignement. De toute façon, pour votre scénario de quatre millions de lignes, l'utilisation de namedtuple signifierait payer (au-delà du coût des valeurs) un surcoût de 275 Mo au total, contre 915 (3,6) - 1100 (3,5) Mo pour dict et 1770 (3,6) - 1950 (3,5) Mo pour OrderedDict. Quand vous parlez d'un système de 8 Go, raser 1,5 Go de vos frais généraux est une amélioration majeure.