Qual è la tecnica di inversione del ciclo?


89

Stavo esaminando un documento che parla delle tecniche di ottimizzazione del compilatore just-in-time (JIT) per Java. Uno di questi era "inversione di ciclo". E il documento dice:

Sostituisci un whileciclo regolare con un do-whileciclo. E il do-whileciclo è impostato all'interno di una ifclausola. Questa sostituzione porta a due salti in meno.

Come funziona l'inversione del ciclo e come ottimizza il nostro percorso di codice?

NB: Sarebbe fantastico se qualcuno potesse spiegare con un esempio di codice Java e come JIT lo ottimizza in codice nativo e perché è ottimale nei processori moderni.


2
Non è qualcosa che faresti con il tuo codice sorgente. Succede a livello di codice nativo.
Marko Topolnik

2
@MarkoTopolnik lo so. Ma voglio sapere come JIT fa questo a livello di codice nativo. Grazie.
Prova il

1
oh bello, c'è una pagina di wikipedia su questo con molti esempi en.wikipedia.org/wiki/Loop_inversion . L'esempio C è altrettanto valido in Java.
Benjamin Gruenbaum

Qualche tempo fa, ispirato da una delle domande su SO, ho condotto una breve ricerca su questo argomento, forse i risultati ti saranno utili: stackoverflow.com/questions/16205843/java-loop-efficiency/…
Adam Siemion

È la stessa cosa di dove la condizione del ciclo viene solitamente posta alla fine (indipendentemente dal fatto che vengano eseguiti meno salti), solo in modo che ci siano meno istruzioni di salto (1 vs 2 per iterazione)?
extremeeaxe5

Risposte:


108
while (condition) { 
  ... 
}

Flusso di lavoro:

  1. controllare le condizioni;
  2. se falso, salta fuori dal ciclo;
  3. eseguire un'iterazione;
  4. salta in cima.

if (condition) do {
  ...
} while (condition);

Flusso di lavoro:

  1. controllare le condizioni;
  2. se falso, salta oltre il ciclo;
  3. eseguire un'iterazione;
  4. controllare le condizioni;
  5. se è vero, vai al passaggio 3.

Confrontando questi due si può facilmente vedere che quest'ultimo potrebbe non fare alcun salto, a condizione che ci sia esattamente un passaggio attraverso il ciclo, e generalmente il numero di salti sarà uno in meno rispetto al numero di iterazioni. Il primo dovrà tornare indietro per controllare la condizione, solo per saltare fuori dal ciclo quando la condizione è falsa.

I salti sulle moderne architetture di CPU in pipeline possono essere piuttosto costosi: poiché la CPU sta terminando l'esecuzione dei controlli prima del salto, le istruzioni oltre quel salto sono già nel mezzo della pipeline. Tutta questa elaborazione deve essere scartata se la previsione del ramo fallisce. L'ulteriore esecuzione viene ritardata mentre la pipeline viene riattivata.

Spiegazione della previsione del ramo citato : per ogni tipo di salto condizionale la CPU ha due istruzioni, ciascuna inclusa una scommessa sul risultato. Ad esempio, potresti mettere un'istruzione che dice " salta se non zero, scommettendo su non zero " alla fine di un ciclo perché il salto dovrà essere fatto su tutte le iterazioni tranne l'ultima. In questo modo la CPU inizia a pompare la sua pipeline con le istruzioni che seguono l'obiettivo di salto invece di quelle che seguono l'istruzione di salto stessa.

Nota importante

Si prega di non prenderlo come esempio di come ottimizzare a livello di codice sorgente. Ciò sarebbe del tutto fuorviante poiché, come già chiaro dalla tua domanda, la trasformazione dalla prima forma alla seconda è qualcosa che il compilatore JIT fa come una questione di routine, completamente da solo.


51
Quella nota alla fine è davvero una cosa molto, molto importante.
TJ Crowder

2
@AdamSiemion: il bytecode generato per il do-whilecodice sorgente specificato è irrilevante, perché in realtà non lo scriviamo. Scriviamo il whileciclo e lasciamo che il compilatore e JIT cospirino per migliorarlo per noi (tramite inversione del ciclo) se / come necessario.
TJ Crowder

1
@TJCrowder +1 per quanto sopra, più la nota per Adam: non prendere mai in considerazione il bytecode quando si pensa alle ottimizzazioni del compilatore JIT. Bytecode è molto più vicino al codice sorgente Java che al codice compilato JIT effettivo in esecuzione. In effetti, la tendenza nelle lingue moderne è quella di non avere affatto il bytecode come parte della specificazione del linguaggio.
Marko Topolnik

1
Sarebbe stato più informativo la nota importante è stata spiegata un po 'di più. Perché sarebbe del tutto fuorviante?
arsaKasra

2
@arsaKasra È fuorviante perché in generale la leggibilità e la stabilità prevalgono sulle ottimizzazioni nel codice sorgente. Soprattutto con la rivelazione che il JIT fa questo per te, non dovresti tentare l'ottimizzazione (molto micro) da solo.
Radiodef

24

Questo può ottimizzare un ciclo che viene sempre eseguito almeno una volta.

Un whileciclo regolare salterà quindi sempre all'inizio almeno una volta e salterà alla fine una volta alla fine. Un esempio di un semplice ciclo in esecuzione una volta:

int i = 0;
while (i++ < 1) {
    //do something
}  

Un do-whileloop invece salterà il primo e l'ultimo salto. Ecco un ciclo equivalente a quello sopra, che verrà eseguito senza salti:

int i = 0;
if (i++ < 1) {
    do {
        //do something
    } while (i++ < 1); 
}

+1 per essere corretto e primo, considera l'aggiunta di un esempio di codice. Qualcosa di simile boolean b = true; while(b){ b = maybeTrue();}al boolean b;do{ b = maybeTrue();}while(b);dovrebbe essere sufficiente.
Benjamin Gruenbaum

Nessun problema. In un certo senso invalida la riga di apertura della risposta, fwiw. :-)
TJ Crowder

@TJ Bene, non ottimizzerà ancora un ciclo che non è stato inserito, ci sarà un salto in entrambi i casi.
Keppil

Ah sì. Scusa, stavo leggendo questo per significare che non puoi applicarlo a loop che non si ripetono almeno una volta (piuttosto che non li aiuta). Con te adesso. :-)
TJ Crowder

@Keppil Probabilmente dovresti rendere esplicito che nel caso in cui abbiamo un gran numero di iterazioni X, allora salveremo solo un singolo salto tra quelli X.
Manuel Selva

3

Vediamoli:

La whileversione:

void foo(int n) {
    while (n < 10) {
       use(n);
       ++n;
    }
    done();
}
  1. Per prima cosa testiamo ne passiamo a done();se la condizione non è vera.
  2. Quindi usiamo e incrementiamo n.
  3. Ora torniamo alla condizione.
  4. Risciacquare, ripetere.
  5. Quando la condizione non è più vera, passiamo a done().

La do-whileversione:

(Ricorda, in realtà non lo facciamo nel codice sorgente [che introdurrebbe problemi di manutenzione], il compilatore / JIT lo fa per noi.)

void foo(int n) {
    if (n < 10) {
        do {
            use(n);
            ++n;
        }
        while (n < 10);
    }
    done();
}
  1. Per prima cosa testiamo ne passiamo a done();se la condizione non è vera.
  2. Quindi usiamo e incrementiamo n.
  3. Ora testiamo la condizione e torniamo indietro se è vera.
  4. Risciacquare, ripetere.
  5. Quando la condizione non è più vera, fluiamo (non saltiamo) verso done().

Quindi, ad esempio, se ninizia 9, non saltiamo mai nella do-whileversione, mentre nella whileversione dobbiamo tornare all'inizio, fare il test e poi tornare alla fine quando vediamo che non è vero .


3

L'inversione di loop è una tecnica di ottimizzazione delle prestazioni che migliora le prestazioni poiché il processore può ottenere lo stesso risultato con meno istruzioni. Ciò dovrebbe principalmente migliorare le prestazioni in condizioni al contorno.

Questo collegamento fornisce un altro esempio di inversione del ciclo. In poche architetture in cui il decremento e il confronto sono implementati come un singolo set di istruzioni, ha senso convertire un ciclo for in un while con l'operazione di decremento e confronto.

Wikipedia ha un ottimo esempio e lo sto spiegando di nuovo qui.

 int i, a[100];
  i = 0;
  while (i < 100) {
    a[i] = 0;
    i++;
  }

verrà convertito dal compilatore in

  int i, a[100];
  i = 0;
  if (i < 100) {
    do {
      a[i] = 0;
      i++;
    } while (i < 100);
  }

Come si traduce in prestazioni? Quando il valore di i è 99, il processore non ha bisogno di eseguire un GOTO (che è richiesto nel primo caso). Ciò migliora le prestazioni.

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.