Please ignore secret bonuses. Secret tests do NOT award bonus. Max hw grade is 30+2 bonus efficiency

Do you need help?

Notice Board

Ciclo while e ciclo for: velocità a confronto

edoardo (7090 points)
19 52 67
in Programmare in Python by (7.1k points)
edited by
Ciao a tutti ragazzi,

stamattina mi son divertito a scrivermi un po' di funzioni quando ho avuto l'idea di provare a vedere quale ciclo, tra il for e il while, fosse il più veloce. Mi son scritto una semplice funzione, praticamente identica in entrambi i casi, fatta eccezione per il ciclo, che mi scorresse tanti numeri. Ho fatto due test: il primo dove le due funzioni mi scorrevano un milione di numeri, il secondo dove mi scorrevano due milioni di numeri. In entrambi le funzioni stampo il valore della variabile a + 1, quindi scrivo anche in console prima il milione di numeri e dopo i 2 milioni.

Per calcolarmi il tempo di esecuzione ho usato la libreria time, che mi ha permesso di usare due una funzione time() con la quale ottengo il tempo attuale. Comunque, nel primo caso ho notato che la differenza tra i due cicli era davvero poca:

il for ha impiegato 87.0873475075768 secondi per scorrere un milione di numeri;

il while ha impiegato 87.13061666488647 secondi.

La differenza è praticamente nulla. A questo punto ho aumentato a due milioni e i risultati sono i seguenti:

per il for 176.6266007423401 secondi;

per il while 180.3107681274414​ secondi;

Una differenza di quasi 4 secondi.

La mia curiosità è: è più performante un ciclo while o un ciclo for? E praticamente, c'è una vera e propria differenza nell'elaborazione del ciclo?
5.5k views
closed

6 Answers

Best answer
daniele.gargano (1200 points)
3 7 16
by (1.2k points)
edited by

Ciao edoardo,

Per risponderti occorre andare a sbirciare il Bytecode delle relative funzioni, e grazie al modulo dis ci vogliono veramente pochi attimi. 
Basta scrivere due funzioni basilari che contengano tutto il necessario a fare il più semplice dei cicli while e for, e dopodiché disassemblrarle.

Ecco il Bytecode della funzione while:

  4           0 LOAD_CONST               1 (0)
              2 STORE_FAST               0 (i)

  5           4 SETUP_LOOP              20 (to 26)
        >>    6 LOAD_FAST                0 (i)
              8 LOAD_CONST               2 (5)
             10 COMPARE_OP               0 (<)
             12 POP_JUMP_IF_FALSE       24

  6          14 LOAD_FAST                0 (i)
             16 LOAD_CONST               3 (1)
             18 INPLACE_ADD
             20 STORE_FAST               0 (i)
             22 JUMP_ABSOLUTE            6
        >>   24 POP_BLOCK

  7     >>   26 LOAD_CONST               0 (None)
             28 RETURN_VALUE

Ecco il Bytecode della funzione for:

 10           0 SETUP_LOOP              16 (to 18)
              2 LOAD_GLOBAL              0 (range)
              4 LOAD_CONST               1 (5)
              6 CALL_FUNCTION            1
              8 GET_ITER
        >>   10 FOR_ITER                 4 (to 16)
             12 STORE_FAST               0 (num)

 11          14 JUMP_ABSOLUTE           10
        >>   16 POP_BLOCK

 12     >>   18 LOAD_CONST               0 (None)
             20 RETURN_VALUE

Quindi in definitiva possiamo affermare che il for è leggermente più performante del while (anche se in realtà, come ha già sottolineato francesco.dev, andrebbero utilizzati in contesti completamente diversi), in quanto il while deve:

  1. caricare l'indice
  2. caricare il valore con il quale compararlo
  3. comparare i due valori
  4. caricare l'indice
  5. caricare il valore da sommare ad esso
  6. effettuare la somma
  7. salvare il valore dell'indice

mentre il for deve solamente:

  1. caricare range nella stack
  2. caricare il valore da usare come parametro di range
  3. chiamare la funzione range, che genererà un iteratore e lo metterà in cima alla stack
  4. chiamare x volte la funzione next() dell'iteratore creato da range (questo compito è svolto dall'istruzione FOR_ITER), che metterà in cima alla stack il valore che dovrà essere salvato in num (poiché for num in range(x))
  5. salvare il valore in cima alla stack nella variabile num

Se ti interessa approfondire cosa fanno i vari comandi Bytecode utilizzati da Python, dai un'occhiata qui 

francesco.dev (33560 points)
23 51 129
by (33.6k points)
Molto interessante il tuo studio! Finalmente abbiamo trovato una risposta chiara al quesito "Chi vince tra while e for a livello di esecuzione?"
BlackStartx (1910 points)
7 16 30
by (1.9k points)

Una domanda: 

Nel ciclo for hai usato una lista generata progressivamente da un generatore, mentre nel ciclo while hai inserito una condizione vincolata dal valore di 'a', il quale viene sempre incrementato di uno a fine di ogni ciclo?

È corretto?

edoardo (7090 points)
19 52 67
by (7.1k points)
In tutti e due i casi ho utilizzato una variabile da conteggio. Mi spiego: nel while ho fatto che se a < var (var è la variabile che assegno come argomento della funzione) allora esegue il codice, e incremento a di 1 ogni volta.

Per quanto riguarda il for, invece, ho utilizzato la funzione range(0, var). In questo modo fino a quando il mio for non raggiunge var incrementavo la variabile a di 1, e la stampavo in console.
Spero d'esserti stato abbastanza chiaro
by (9.9k points)

La mia curiosità è: è più performante un ciclo while o un ciclo for? E praticamente, c'è una vera e propria differenza nell'elaborazione del ciclo?

Per conoscere la risposta bisognerebbe vedere come gestisce internamente Python i due cicli. Non conosco la risposta in questo senso, però dai risultati che però hai ottenuto, e secondo anche una certa logica, la differenza dei due cicli non esiste nella pratica, ma nella logica del programma; ossia si sceglie di usare while o for in base all'utilizzo.

Mi hanno sempre insegnato ad usare i cicli while e do-while solo quando non si conosce a propri volte verrà ripetuto il codice, mentre si usa il for quando sappiamo a priori quante volte verrà iterato (come in questo caso). È una pura convenzione che serve a migliorare la leggibilità e la formattazione del codice.

Spero di esserti stato d'aiuto!

francesco.dev (33560 points)
23 51 129
by (33.6k points)
Esatto, ema-pe!
Anche io la penso allo stesso modo, ovvero al di là delle performance che hanno i diversi cicli, questi esistono per coprire degli scopi ben precisi!
Usarli come capita è segno di cattiva programmazione e di non seguire quella, come dici tu, formattazione del codice che permetterebbe di aumentare il grado di leggibilità dello stesso.
LeonardoEmili (35740 points)
14 65 155
by (35.7k points)

Ciao!
E' davvero interessante il test che ci proponi, tuttavia devo chiederti: hai effettuato più test? E hai provato a cambiare macchina da dove eseguirli? Devi tener conto che la velocità di elaborazione del processore dipende da moltissime variabili, quali: il carico di lavoro che deve elaborare, la temperatura a cui la macchina si trova, e moltissimi altri ancora...
Arrivo ora a rispondere alla tua domanda..
Avendo avuto esperienze anche con altri linguaggi ho notato che la differenza tra un ciclo for e un ciclo while sta proprio nella loro sintassi, e nel loro "modo di organizzarsi".
Per capire meglio ti faccio un esempio: quando tu vuoi utilizzare un ciclo for in moltissimi linguaggi la sintassi è: inizializzazione variabile; verifica della condizione; "azione" della variabile (tipicamente incremento o decremento di n).
Col ciclo while invece tu non fai altro che controllare una condizione, se questa è vera il blocco di codice verrà eseguito, altrimenti no. 
Ti faccio però notare che un ciclo for sarebbe ugualmente valido se al primo e al terzo parametro non gli passassimo nulla e dessimo invece al secondo la condizione da verificare. La sintassi sarebbe (;"condizione";) . Dato che la condizione prende in esame una variabile già esistente, questa questa non deve esser di nuovo inizializzata e infine il famoso "incremento/decremento" potrebbe esser inserito all'interno del codice da eseguire.
Detto questo spero di esser stato chiaro nell'esposizione e di averti aiutato :)

Leonardo Emili

edoardo (7090 points)
19 52 67
by (7.1k points)
In effetti, successivamente, avevo eseguito altri test, che comunque sia hanno "confermato", se così si può dire, che il for mi eseguiva questo scorrimento più velocemente. A volte di pochissimo, a volte di qualche secondo (un intervallo compreso tra 3-6 secondi).

In c# mi è capitato di avere qualche eccezione di "Indice fuori la matrice" con il ciclo for, e son stato costretto ad utilizzare un ciclo while (e, più o meno, a rifarlo completamente!).
LeonardoEmili (35740 points)
14 65 155
by (35.7k points)
Perfetto hai potuto constatare tu stesso come i due cicli abbiano "velocità" variabili relative all'ambiente dove questi vengono eseguiti. In definitiva ti consiglio di utilizzare il ciclo for, quando possibile, proprio perché è di "più facile lettura" , e nel caso questo non sia possibile ricorrere al while.
francesco.dev (33560 points)
23 51 129
by (33.6k points)
LeonardoEmili, concordo con quello che dici, però bisogna ricordare il differente scopo logico di un ciclo for rispetto ad un ciclo while e do-while.
Infatti usiamo un ciclo for quando sappiamo a priori il numero di volte in cui tale ciclo dovrà essere ripetuto (e quindi eseguire le istruzioni all'interno di esso); invece usiamo un ciclo while/do-while quando non sappiamo quante volte andranno eseguite le varie istruzioni presenti nel ciclo.

Infine, per chi non lo sapesse, un ciclo while [DEFINITO PRE-CONDIZIONALE] presenta la condizione di verifica all'inizio e quindi questa è la prima cosa che viene eseguita;
invece un ciclo do-while [DEFINITO POST-CONDIZIONALE] presenta la condizione di verifica alla fine e quindi verranno eseguite, almeno una volta, il corpo di istruzioni presenti nel ciclo.
LeonardoEmili (35740 points)
14 65 155
by (35.7k points)
Si sono d'accordo con quello che dici, infatti a parità di condizioni essi possono essere usati in maniera pressoché ambivalente. Tuttavia nel caso in cui conosciamo già quante volte vogliamo iterare tale blocco di codice èpiù conveniente usare il ciclo for, poiché risulta esser più compatto e di più semplice comprensione.
francesco.dev (33560 points)
23 51 129
by (33.6k points)
Ciao edoardo!
Allora, avendo in mano i dati dei tuoi risultati, potrei affermare che il meccanismo "interno" di entrambi i cicli è pressoché identico.
Purtroppo neanche io conosco come Python li gestisce realmente, però a livello logico mi viene da affermare ciò che ho appena detto.

Quindi, al di là delle performance che sembrano simili, ti ricordo che a livello di composizione del codice e di "buona programmazione" usiamo il ciclo while e il ciclo for per due motivi ben diversi.
Infatti usiamo il primo quando non sappiamo il numero di volte in cui andranno eseguite le istruzioni all'interno del ciclo; invece usiamo il secondo quando a propri conosciamo il numero di volte in cui bisognerà eseguire le istruzioni presenti all'interno del ciclo.

Ad esempio:
Se dobbiamo ripetere per 10 volte la parola "ciao", useremo semplicemente il ciclo for.
Se, invece, dobbiamo sommare dei numeri casuali finché tale somma è minore di 1000, allora ci conviene usare un ciclo while/do-while. Questo non perché altrimenti il for ci dà problemi, ma semplicemente perché è buona norma e segno di buona programmazione usarli per il loro preciso scopo.

Sperando di essere stato d'aiuto, ciao!
edoardo (7090 points)
19 52 67
by (7.1k points)
Ti ringrazio per la spiegazione del do-while. Non ho mai programmato a scuola, non essendoci programmazione di alcun genere nel mio vecchio liceo, e ciò che so riguardo tutto il mondo della programmazione è puramente nato da un mio hobby.

I cicli for e while li ho quasi sempre utilizzati in c#, ho sempre usato anche foreach e while in php, che alla fine sono quasi cicli identici.
Per quanto riguarda i do-while non mi è mai capitato di usarli, e non ho mai "capito" quando doverli usare. Avrei sempre utilizzato un while condizionato, ti ringrazio per questa breve e concisa spiegazione!
francesco.dev (33560 points)
23 51 129
by (33.6k points)
Di niente, è stato un piacere aiutarti!
Infine ricorda che:
Un ciclo pre-condizionale, come il while, presenta la condizione del ciclo all'inizio e quindi questa è la prima cosa che viene eseguita;
invece un ciclo post-condizionale, come il do-while, presenta la condizione del ciclo alla fine e questo significa che almeno una volta le istruzioni presenti nel ciclo verranno eseguite.

Se non ti è chiaro sarò più che disponibile a darti chiarimenti!
Purtroppo non metto degli esempi di codice per farti capire meglio poiché questo andrebbe contro il regolamento.
Gabriele97 (2010 points)
12 29 38
by (2.0k points)

Ciao, oltre al tempo di esecuzione è sicuramente importante verificare quale sia lo spazio di memoria temporanea occupata per eseguire un istruzione. Da quello che so è preferibile utilizzare un ciclo while solo quando non si può utilizzare il loop di for, il motivo penso sia essenzialmente la leggibilità del programma; mi spiego meglio:

Prima di iniziare il ciclo è necessario dichiarare una variabile "i" utilizzata per il conteggio ed inizializzarla. La variabile "i" deve essere incrementata all'interno del blocco.
Mentre, se si usa il costrutto "for"  il codice diventa più compatto e leggibile(magari per te che riprenderai in mano il codice dopo tanto tempo o per altre persone a cui lo passerai) tanto è che un ciclo in una sola riga for ci dice dove inizializzare il ciclo e dove terminarlo. Inoltre al termine del ciclo while la "variabile contatore" rimane in memoria.

edoardo (7090 points)
19 52 67
by (7.1k points)
È vero anche questo! Infatti ho "aumentato" il consumo della RAM (16gb, aprendo qualche programma) e devo dire che sia il while e il for hanno avuto un rallentamento nella loro esecuzione.