2016-04-12 17 views
5

Durante l'esecuzione di un integratore numerico, ho notato una notevole differenza in termini di velocità a seconda di come ho estratto il valore del campo in un dizionarioPerché la dict.get (chiave) correre più lento di dict [tasto]

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)) 

Sul mio sistema, ho notato quanto segue differenza (usando 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 

Rispetto al

%%timeit 
good_get(name_dict) 

1000000 loops, best of 3: 188 ns per loop 

Questa potrebbe sembrare una piccola differenza, ma in alcuni array la differenza sembra essere ancora più drammatica. Che cosa causa questo comportamento, ed è in qualche modo che dovrei modificare il mio uso della funzione get()?

+0

Buona osservazione. Se cerchi la velocità, puoi sostituire 'mydict.get (" nome ")' in 'mydict [" nome "]' in try/except block, catturando 'KeyError' e assegnando' None' lì. –

risposta

9

Python ha a che fare più lavoro per dict.get():

  • get è un attributo, quindi Python deve guardare questo in su, e quindi associare il descrittore trovato all'istanza dizionario.
  • () è una chiamata, quindi il frame corrente deve essere inserito nello stack, è necessario effettuare una chiamata, quindi il frame deve essere nuovamente estratto dallo stack per continuare.

Il [...] notazione, utilizzato con un dict, non richiede una fase di attributo separato o telaio di spinta e pop.

Si può vedere la differenza quando si utilizza il 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 

quindi l'espressione d[key] deve solo eseguire un codice operativo BINARY_SUBSCR, mentre d.get(key) aggiunge un codice operativo LOAD_ATTR. CALL_FUNCTION è molto più costoso di BINARY_SUBSCR su un tipo predefinito (i tipi personalizzati con i metodi __getitem__ continuano a eseguire una chiamata di funzione).

Se la maggior parte delle vostre chiavi presenti nel dizionario, si potrebbe usare try...except KeyError per gestire le chiavi mancanti:

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

La gestione delle eccezioni è a buon mercato se non ci sono eccezioni.

+0

Perché non possiamo revocare e revocare nuovamente tali risposte positive. Apprezza il tempo che hai dedicato a spiegare il problema con grande conoscenza .. :) –