2016-04-12 2 views
5

Lors de l'exécution d'un intégrateur numérique, j'ai remarqué une différence notable dans la vitesse en fonction de la façon dont j'extraire la valeur du champ dans un dictionnairedict.get (clé) exécuter plus lentement que dict [key]

import numpy as np 

def bad_get(mydict): 
    '''Extract the name field using get()''' 
    output = mydict.get('name', None) 
    return output 

def good_get(mydict): 
    '''Extract the name field using if-else''' 
    if 'name' in mydict: 
     output = mydict['name'] 
    else: 
     output = None 
    return output 


name_dict = dict() 
name_dict['name'] = np.zeros((5000,5000)) 

Sur mon système, je remarque la différence suivante (en utilisant ipython)

%%timeit 
bad_get(name_dict) 

The slowest run took 7.75 times longer than the fastest. This could mean that an intermediate result is being cached. 
1000000 loops, best of 3: 247 ns per loop 

Par rapport à

%%timeit 
good_get(name_dict) 

1000000 loops, best of 3: 188 ns per loop 

Cela peut sembler une petite différence, mais pour certains tableaux, la différence semble être encore plus dramatique. Qu'est-ce qui cause ce comportement, et y a-t-il un moyen de modifier mon utilisation de la fonction get()?

+0

Bonne observation. Si vous chassez pour la vitesse, vous pouvez remplacer 'mydict.get ("nom")' 'à mondict [ "nom"]' dans try/except, attrapant '' KeyError' et l'attribution none' là. –

Répondre

9

Python doit faire plus de travail pour dict.get():

  • get est un attribut, alors Python doit regarder ça, et se lient alors le descripteur trouvé à l'instance dictionnaire.
  • () est un appel, de sorte que le cadre actuel doit être poussé sur la pile, un appel doit être fait, le cadre doit être à nouveau sauté de la pile pour continuer.

La notation [...], utilisé avec un dict, ne nécessite pas une étape d'attribut séparé ou pousser cadre et pop.

Vous pouvez voir la différence lorsque vous utilisez le Python bytecode disassembler dis:

>>> import dis 
>>> dis.dis(compile('d[key]', '', 'eval')) 
    1   0 LOAD_NAME    0 (d) 
       3 LOAD_NAME    1 (key) 
       6 BINARY_SUBSCR 
       7 RETURN_VALUE 
>>> dis.dis(compile('d.get(key)', '', 'eval')) 
    1   0 LOAD_NAME    0 (d) 
       3 LOAD_ATTR    1 (get) 
       6 LOAD_NAME    2 (key) 
       9 CALL_FUNCTION   1 
      12 RETURN_VALUE 

donc l'expression d[key] n'a qu'à exécuter une BINARY_SUBSCR opcode, tandis que d.get(key) ajoute une LOAD_ATTR opcode. CALL_FUNCTION est beaucoup plus cher que BINARY_SUBSCR sur un type intégré (types personnalisés avec __getitem__ méthodes finissent encore faire un appel de fonction).

Si la majorité de vos clés existent dans le dictionnaire, vous pouvez utiliser try...except KeyError pour gérer les clés manquantes:

try: 
    return mydict['name'] 
except KeyError: 
    return None 

La gestion des exceptions est pas cher s'il n'y a pas d'exceptions.

+0

Pourquoi ne pouvons-nous upvote et upvote encore une fois ces bonnes réponses .. apprécient le temps passé à expliquer la question avec une grande connaissance .. :) –