2016-02-05 30 views
17

Qual è la ragione per cui la trasmissione di un intero a un float è più lenta dell'aggiunta di 0.0 a quell'int in Python?Perché chiamare float() su un numero più lento rispetto all'aggiunta di 0.0 in Python?

import timeit 


def add_simple(): 
    for i in range(1000): 
     a = 1 + 0.0 


def cast_simple(): 
    for i in range(1000): 
     a = float(1) 


def add_total(): 
    total = 0 
    for i in range(1000): 
     total += 1 + 0.0 


def cast_total(): 
    total = 0 
    for i in range(1000): 
     total += float(1) 


print "Add simple timing: %s" % timeit.timeit(add_simple, number=1) 
print "Cast simple timing: %s" % timeit.timeit(cast_simple, number=1) 
print "Add total timing: %s" % timeit.timeit(add_total, number=1) 
print "Cast total timing: %s" % timeit.timeit(cast_total, number=1) 

la cui uscita è:

Aggiungere semplice temporizzazione: 0,0001220703125

Cast semplice temporizzazione: 0,000469923019409

Aggiungere temporizzazione totale: 0,000164985656738

temporizzazione totale Cast: 0.00040078163147

+2

Qual è l'output di 'dis.dis'? –

+0

In questo modo, cosa ti aspetti di essere più veloce? 'a =" 100 "' o 'b = str (100)'? –

+0

@Jeff: il tuo esempio è qualcosa di diverso. Sarebbe più come 'float (100)' vs '100.0', quando la domanda riguarda' float (100) 'vs' 100 + 0.0'. – GingerPlusPlus

risposta

15

Se si utilizza il modulo dis, si può iniziare a capire perché:

In [11]: dis.dis(add_simple) 
    2   0 SETUP_LOOP    26 (to 29) 
       3 LOAD_GLOBAL    0 (range) 
       6 LOAD_CONST    1 (1000) 
       9 CALL_FUNCTION   1 (1 positional, 0 keyword pair) 
      12 GET_ITER 
     >> 13 FOR_ITER    12 (to 28) 
      16 STORE_FAST    0 (i) 

    3   19 LOAD_CONST    4 (1.0) 
      22 STORE_FAST    1 (a) 
      25 JUMP_ABSOLUTE   13 
     >> 28 POP_BLOCK 
     >> 29 LOAD_CONST    0 (None) 
      32 RETURN_VALUE 

In [12]: dis.dis(cast_simple) 
    2   0 SETUP_LOOP    32 (to 35) 
       3 LOAD_GLOBAL    0 (range) 
       6 LOAD_CONST    1 (1000) 
       9 CALL_FUNCTION   1 (1 positional, 0 keyword pair) 
      12 GET_ITER 
     >> 13 FOR_ITER    18 (to 34) 
      16 STORE_FAST    0 (i) 

    3   19 LOAD_GLOBAL    1 (float) 
      22 LOAD_CONST    2 (1) 
      25 CALL_FUNCTION   1 (1 positional, 0 keyword pair) 
      28 STORE_FAST    1 (a) 
      31 JUMP_ABSOLUTE   13 
     >> 34 POP_BLOCK 
     >> 35 LOAD_CONST    0 (None) 
      38 RETURN_VALUE 

Nota La funzione CALL_FUNCTION

chiama in Python sono (relativamente) lento. Come sono le ricerche .. Perché la trasmissione a float richiede una chiamata di funzione: ecco perché è più lenta.

+0

Ok, quindi vedo il punto in cui le chiamate di funzione sono lente, vuol dire che stai sempre meglio usare '+ 0.0' invece di chiamare 'float()'? – user22490234

+16

Dalla tua domanda - la differenza è tra '0.0001220703125' e' 0.000469923019409' - per ** '1000' ** di queste operazioni. Ciò significa che stai osservando un costo straordinariamente * piccolo * indipendentemente dall'opzione che hai scelto. Se ti capita di profilare il tuo codice e vedi che stai chiamando 'float (number)' un miliardo di volte, e stai cercando di farlo andare più veloce perché * direttamente * significa più soldi, allora potrebbe valere la pena di usarlo ' + 0.0', ma altrimenti i problemi di leggibilità che cercherete di ricordare perché avete fatto '+ 0.0' sono * molto * più costosi di quelli che chiamano la funzione' float() '. –

+2

Questo mostra solo parte della storia. Ci sono 3 fattori principali che contribuiscono alla differenza di prestazioni: piegatura costante, ricerca incorporata e overhead di chiamata di funzione. Questa risposta menziona solo il sovraccarico della chiamata di funzione. – user2357112

3

Facendo l'aggiunta può essere fatto in C. Facendo il cast si fa chiamare una funzione e poi si avvia la C lib. C'è un sovraccarico per quella chiamata di funzione.

7

Se si utilizza Python 3 o una versione recente di Python 2 (2.5 o successiva), esegue il piegamento costante al momento della generazione di bytecode. Ciò significa che 1 + 0.0 viene sostituito con 1.0 prima che il codice venga eseguito.

11

Se si guarda il bytecode add_simple:

>>> dis.dis(add_simple) 
    2   0 SETUP_LOOP    26 (to 29) 
       3 LOAD_GLOBAL    0 (range) 
       6 LOAD_CONST    1 (1000) 
       9 CALL_FUNCTION   1 
      12 GET_ITER 
     >> 13 FOR_ITER    12 (to 28) 
      16 STORE_FAST    0 (i) 

    3   19 LOAD_CONST    4 (1.0) 
      22 STORE_FAST    1 (a) 
      25 JUMP_ABSOLUTE   13 
     >> 28 POP_BLOCK 
     >> 29 LOAD_CONST    0 (None) 
      32 RETURN_VALUE 

Vedrai che 0.0 non è in realtà da nessuna parte in là. Carica solo la costante 1.0 e la memorizza su a. Python ha calcolato il risultato in fase di compilazione, quindi in realtà non stai calcolando l'aggiunta.

Se si utilizza una variabile per 1, così primitivo spioncino ottimizzatore di Python non può fare l'aggiunta al momento della compilazione, l'aggiunta di 0.0 ha ancora un vantaggio:

>>> timeit.timeit('float(a)', 'a=1') 
0.22538208961486816 
>>> timeit.timeit('a+0.0', 'a=1') 
0.13347005844116211 

Calling float richiede due ricerche dict per capire fuori che cosa è float, uno nel namespace globale del modulo e uno nei built-in. Ha anche overhead di chiamata di funzione Python, che è più costoso di una chiamata di funzione C.

calcolata 0.0 richiede solo indicizzazione in co_consts di codice oggetto della funzione per caricare la costante 0.0, e quindi chiamando il livello C nb_add funzioni dei tipi int e float effettuare l'aggiunta. Questa è una quantità inferiore di spese generali generali.

+2

+1, sapevo che il folding costante avrebbe rimosso l'aggiunta, ma non mi sono fermato a considerare che l'aggiunta sarebbe stata ancora più veloce della chiamata 'float', se dovessi forzarla a succedere. –

2

In parole semplici, non si sta trasmettendo nulla. Un cast di tipo dice al compilatore di trattare il valore in una variabile come se avesse un tipo diverso; vengono utilizzati gli stessi bit sottostanti.Python float(1), tuttavia, costruisce un nuovo oggetto in memoria distinto dall'argomento su float.

Quando si aggiunge 1 + 0.0, questo chiama semplicemente (1).__add__(0.0), e il metodo della classe di int incorporato __add__ sa come trattare con float oggetti. Non è necessario costruire oggetti aggiuntivi (oltre al valore di ritorno).

Le versioni più recenti di Python dispongono di un ottimizzatore in modo che le espressioni costanti come 1 + 0.0 possano essere sostituite in fase di compilazione con 1.0; nessuna funzione deve essere eseguita in fase di esecuzione. Sostituire con x = 1 (prima del ciclo) e x + 0.0 per forzare int.__add__ da richiamare in runtime per osservare la differenza. Sarà più lento di 1 + 0.0, ma ancora più veloce di float(1).

+3

"Un cast di tipo dice al compilatore di trattare il valore in una variabile come se avesse un tipo diverso, vengono usati gli stessi bit sottostanti" - dove hai preso quell'idea? Non è come "reinterpret_cast" è l'unico tipo di cast in circolazione. La maggior parte dei calchi nella maggior parte dei linguaggi comporta la modifica della rappresentazione del valore in memoria. – user2357112