Python ottimizza una variabile che viene utilizzata solo come valore di ritorno?


106

C'è qualche differenza fondamentale tra i seguenti due frammenti di codice? Il primo assegna un valore a una variabile in una funzione e quindi restituisce quella variabile. La seconda funzione restituisce direttamente il valore.

Python li trasforma in bytecode equivalente? Uno di loro è più veloce?

Caso 1 :

def func():
    a = 42
    return a

Caso 2 :

def func():
    return 42

5
Se lo usi dis.dis(..)su entrambi vedi che c'è una differenza , quindi sì. Ma nella maggior parte delle applicazioni del mondo reale , il sovraccarico di questo rispetto al ritardo dell'elaborazione nella funzione non è così tanto.
Willem Van Onsem

4
Ci sono due possibilità: (a) Chiamerai questa funzione molte (cioè almeno un milione) volte in un ciclo stretto. In tal caso non dovresti chiamare affatto una funzione Python, ma invece dovresti vettorializzare il tuo ciclo usando qualcosa come la libreria numpy. (b) Non chiamerai questa funzione così tante volte. In questo caso la differenza di velocità tra queste funzioni è troppo piccola per preoccuparsi.
Arthur Tacca

Risposte:


138

No, non è così .

La compilazione in byte code CPython viene passata solo attraverso un piccolo ottimizzatore di spioncino progettato per eseguire solo ottimizzazioni di base (vedere test_peepholer.py nella suite di test per ulteriori informazioni su queste ottimizzazioni).

Per dare un'occhiata a quello che sta per accadere, usa dis* per vedere le istruzioni generate. Per la prima funzione, contenente l'assegnazione:

from dis import dis
dis(func)
  2           0 LOAD_CONST               1 (42)
              2 STORE_FAST               0 (a)

  3           4 LOAD_FAST                0 (a)
              6 RETURN_VALUE

Mentre, per la seconda funzione:

dis(func2)
  2           0 LOAD_CONST               1 (42)
              2 RETURN_VALUE

Nella prima vengono utilizzate altre due istruzioni (veloci): STORE_FASTe LOAD_FAST. Questi fanno una rapida memorizzazione e cattura del valore fastlocalsnell'array del frame di esecuzione corrente. Quindi, in entrambi i casi, RETURN_VALUEviene eseguita una. Quindi, il secondo è leggermente più veloce a causa di meno comandi necessari per l'esecuzione.

In generale, tieni presente che il compilatore CPython è conservatore nelle ottimizzazioni che esegue. Non è e non cerca di essere intelligente come altri compilatori (che, in generale, hanno anche molte più informazioni con cui lavorare). L'obiettivo principale del design, oltre ad essere ovviamente corretto, è a) mantenerlo semplice eb) essere il più rapido possibile nel compilarli in modo da non notare nemmeno che esiste una fase di compilazione.

Alla fine, non dovresti preoccuparti di piccoli problemi come questo. Il vantaggio in termini di velocità è minimo, costante e, sminuito dall'overhead introdotto dal fatto che Python viene interpretato.

* disè un piccolo modulo Python che disassembla il codice, puoi usarlo per vedere il bytecode Python che la VM eseguirà.

Nota: come affermato anche in un commento di @Jorn Vernee, questo è specifico per l'implementazione CPython di Python. Altre implementazioni potrebbero fare ottimizzazioni più aggressive se lo desiderano, CPython no.


11
Non sono una persona Python (c ++) quindi non so come funziona sotto il cofano, ma il primo caso non dovrebbe essere ottimizzato per il secondo caso? Un compilatore C ++ decente farebbe quell'ottimizzazione.
NathanOliver

7
@NathanOliver in realtà non lo fa, Python farà come detto qui senza nemmeno tentare di giocare in modo intelligente.
Dimitris Fasarakis Hilliard

80
Il fatto che l'ipotesi perfettamente ragionevole e intelligente di @ NathanOliver su una risposta a questa domanda sia completamente sbagliata è, ai miei occhi, la prova che questa non è una domanda "autoesplicativa", "senza senso", "stupida" a cui è possibile rispondere "prendendoci un momento per pensare", come TigerhawkT3 vorrebbe farci credere. È una domanda valida e interessante a cui non ero certo della risposta nonostante fossi stato un programmatore professionista Python per anni.
Mark Amery

Il compilatore di Python è nella migliore delle ipotesi "conservatore", non "molto conservatore". L'obiettivo principale del design non è quello di essere "il più rapido possibile ... quindi non ti accorgi nemmeno che esiste una fase di compilazione". Questo è secondario, dopo "mantenerlo semplice". Una funzione con costanti grandi come "1 << (2 ** 34)" e "b'x '* (2 ** 32)" impiega diversi secondi per essere compilata e generare costanti di dimensioni GB, anche se la funzione non è mai correre. La stringa grande verrà anche scartata dal compilatore. Le correzioni proposte per questi casi sono state rifiutate in quanto renderebbero il compilatore troppo complesso.
Andrew Dalke

@AndrewDalke grazie per il commento degli addetti ai lavori su questo, ho modificato la formulazione per affrontare i problemi che hai segnalato.
Dimitris Fasarakis Hilliard

3

Entrambi sono fondamentalmente uguali tranne che nel primo caso l'oggetto 42viene semplicemente assegnato a una variabile denominata ao, in altre parole, i nomi (cioè a) si riferiscono a valori (cioè 42). Tecnicamente non esegue alcun compito, nel senso che non copia mai alcun dato.

Durante l' returning, questa associazione con nome aviene restituita nel primo caso mentre l'oggetto 42viene restituito nel secondo caso.

Per ulteriori letture, fare riferimento a questo fantastico articolo di Ned Batchelder

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.