2010-07-30 16 views
12

una domanda rapida e semplice da parte mia su for-loops.C# - Interni del ciclo intermedio

Situazione Attualmente sto scrivendo un codice ad alte prestazioni, quando improvvisamente mi chiedevo come il ciclo for in realtà si comporta. So di essermi imbattuto in questo, ma non riesco a trovare nuovamente queste informazioni:/

Ancora, la mia preoccupazione principale era con il limitatore. Dire che abbiamo:

for(int i = 0; i < something.awesome; i++) 
{ 
// Do cool stuff 
} 

Domanda Is something.awesome memorizzato come una variabile interna o è il ciclo continuo Recupero something.awesome a fare la logica-check? Perché sto chiedendo è, naturalmente, perché ho bisogno di scorrere un sacco di cose indicizzate e davvero non voglio l'overhead di chiamata di funzione extra per ogni passaggio.

Tuttavia se qualcosa di bello viene chiamato solo una volta, allora torno sotto il mio rock felice! :)

+4

Bene, provalo con 'for (int i = 0; i <(i + 2); ++ i)';) – Dario

+4

Solo un accantonamento; ci * è * uno scenario in cui il JIT fa qualcosa di intelligente qui; con gli array individua il caso comune di

+0

Hmm, in realtà non mi fido molto del JIT da quando sto sviluppando in XNA, che è un po 'bizzarro nel 360. Non essendo poco intuitivo, potrebbe rivelarsi utile per le app per PC. :) – Vectovox

risposta

20

È possibile utilizzare un semplice programma di esempio per verificare il comportamento:

using System; 

class Program 
{ 
    static int GetUpperBound() 
    { 
     Console.WriteLine("GetUpperBound called."); 
     return 5; 
    } 

    static void Main(string[] args) 
    { 
     for (int i = 0; i < GetUpperBound(); i++) 
     { 
      Console.WriteLine("Loop iteration {0}.", i); 
     } 
    } 
} 

L'output è il seguente:

GetUpperBound called. 
Loop iteration 0. 
GetUpperBound called. 
Loop iteration 1. 
GetUpperBound called. 
Loop iteration 2. 
GetUpperBound called. 
Loop iteration 3. 
GetUpperBound called. 
Loop iteration 4. 
GetUpperBound called. 

I dettagli di questo comportamento sono descritti nella specifica di linguaggio C# 4.0, sezione 8.3.3 (Troverete e spec all'interno C: \ Programmi \ Microsoft Visual Studio 10.0 \ VC# \ Specifiche \ 1033):

A per istruzione viene eseguita come segue:

  • Se un per- l'inizializzatore è presente, le variabili iniziali o le espressioni sono eseguite nell'ordine sono scritte. Questo passaggio è solo eseguito una volta.

  • Se è presente una condizione, viene valutata.

  • Se la condizione for non è presente o se la valutazione è vera, il controllo viene trasferito alla dichiarazione incorporata. Se e quando raggiunge controllo il punto finale del incorporato dichiarazione (eventualmente dall'esecuzione di un'istruzione continue), le espressioni del per-iteratore, se presenti, sono valutati in sequenza, e poi un'altra iterazione è eseguito, a partire dalla valutazione della condizione nel passaggio precedente.

  • Se la per-condizione è presente e la valutazione cede falso, controllo viene trasferito al punto finale della for.

+0

Grazie per le informazioni extra! Non avevo idea che la risposta fosse di fronte a me tutto il tempo! :) – Vectovox

4

Ha valutato ogni volta. Prova questo in una semplice console app:

public class MyClass 
{ 
    public int Value 
    { 
     get 
     {     
      Console.WriteLine("Value called"); 
      return 3; 
     } 
    } 
} 

utilizzato in questo modo:

MyClass myClass = new MyClass(); 
for (int i = 0; i < myClass.Value; i++) 
{     
} 

si tradurrà in tre righe stampate sullo schermo.

Aggiornamento

Quindi, per evitare questo, si potrebbe in questo modo:

int awesome = something.awesome; 
for(int i = 0; i < awesome; i++) 
{ 
// Do cool stuff 
} 
1

something.awesome saranno rivalutati ogni volta che si passa attraverso il ciclo.

Sarebbe meglio per fare questo:

int limit = something.awesome; 
for(int i = 0; i < limit; i++) 
{ 
// Do cool stuff 
} 
1

Ogni volta che il compilatore retrive il valore di something.awesome e valutare

1

La condizione viene valutata ogni momento, anche recuperare il valore del something.awesome. Se si desidera evitare ciò, impostare una variabile temporanea su something.awesome e confrontarla con la variabile temp.

6

Se something.awesome è un campo è probabile che sia l'accesso ogni volta intorno al ciclo poiché qualcosa nel corpo del ciclo può aggiornarlo. Se il corpo del loop è abbastanza semplice e non richiama alcun metodo (a parte i metodi del compilatore inline), allora il compilatore può essere in grado di dimostrare che è sicuro inserire il valore di something.awesome in un registro. Gli scrittori di compilatori erano soliti permettersi di fare questo poco.

Tuttavia in questi giorni è necessario molto tempo per accedere a un valore dalla memoria principale, ma una volta che il valore è stato letto per la prima volta, viene istruito dalla CPU. Leggere il valore per una seconda volta dalla cache della CPU è molto più veloce nella lettura di un registro, quindi leggerlo dalla memoria principale. Non è raro che una cache della CPU sia centinaia di volte più veloce della memoria principale.

Ora se something.awesome è una proprietà , quindi è in effetti una chiamata di metodo. Il compilatore chiamerà il metodo ogni volta intorno al ciclo. Tuttavia se la proprietà/metodo è solo poche righe di codice, potrebbe essere in linea dal compilatore. Inlineing è quando il compilatore inserisce una copia del codice del metodo direttamente anziché chiamare il metodo, quindi una proprietà che restituisce semplicemente il valore di un campo si comporterà come nell'esempio di campo sopra riportato.

Evan quando la proprietà non è in linea, sarà nella cache della CPU dopo che è stata chiamata la prima volta. Quindi le ales sono molto complesse o il ciclo gira un sacco di volte richiede molto più tempo per chiamare la prima volta, forse più di un fattore 10.

Nei vecchi giorni, era facile da usare perché tutte le operazioni di accesso alla memoria e cpu sono state eseguite nello stesso periodo. In questi giorni la cache del processore può facilmente modificare i tempi per alcuni accessi di memoria e chiamate di metodi da su un fattore di 100. I profiler tendono a supporre che tutti gli accessi alla memoria abbiano lo stesso tempo! Quindi se il tuo profilo ti verrà detto di apportare modifiche che potrebbero non avere alcun effetto nel mondo reale.

Modifica del codice a:

int limit = something.awesome; 
for(int i = 0; i < limit; i++) 
{ 
// Do cool stuff 
} 

Will in alcuni casi sparsi in su, ma rende anche più complesso. Tuttavia

int limit = myArray.length; 
for(int i = 0; i < limit; i++) 
{ 
    myArray[i[ = xyn; 
} 

è più lento quindi

for(int i = 0; i < myArray.length; i++) 
{ 
    myArray[i[ = xyn; 
} 

esempio .net controllare la vincolato di array ogni volta che si accede e hanno logica per rimuovere il controllo quando il ciclo è abbastanza semplice.

Quindi è meglio mantenere il codice semplice e chiaro finché non si può dimostrare che c'è un problema. Guadagni molto meglio spendendo il tuo tempo migliorando la progettazione generale di un sistema, questo è facile da fare se il codice che inizi con è semplice.

+0

Grazie mille per l'ottima lettura! Nella mia situazione attuale, si trattava in effetti di un array, tuttavia mi sono imbattuto in casi in cui non è stato un caso, ad esempio. proprietà. Ma prenderò "tenerlo pulito" sotto pesante considerazione. In secondo luogo, indovinare le mie decisioni è dopotutto controproducente! :) – Vectovox

0

Memorizza alcune variabili e la cancella dopo l'uso.

usando (int limite = something.awesome)
{
for (int i = 0; i < Limiti; i ++) {

// Codice.
}
}

In questo modo non controlla ogni volta.