2010-07-30 5 views
17

Il limite nel seguente ciclo (12332 * 324234) viene calcolato una volta o ogni volta che viene eseguito il ciclo?I limiti dei cicli for vengono calcolati una volta o con ciascun ciclo?

for(int i=0; i<12332*324234;i++) 
{ 
    //Do something! 
} 
+2

possibile duplicato di [C# - Interno interno del ciclo] (http://stackoverflow.com/questions/3369773/c-for-loop-internals) –

+0

@Darin, non proprio. Questo ha usato un metodo/proprietà, non una costante. –

+1

Cattivo esempio; il compilatore ottimizzerà i calcoli statici come quello ovunque li trovi. –

risposta

27

Per questo è calcolato una volta, o più probabilmente 0 volte.

Il compilatore ottimizzerà la moltiplicazione per te.

Tuttavia questo non è sempre il caso se si dispone di qualcosa come.

for(int i=0; i<someFunction();i++) 
{ 
    //Do something! 
} 

Poiché il compilatore non è sempre in grado di vedere ciò che torneranno someFunction. Quindi, anche se someFunction restituisce un valore costante ogni volta, se il compilatore non lo sa, non può ottimizzarlo.

EDIT: Come ha detto MainMa in un commento, si è in questa situazione è possibile eliminare il costo facendo qualcosa di simile:

int limit = someFunction(); 
for(int i=0; i<limit ;i++) 
{ 
    //Do something! 
} 

SE si è certi che il valore di someFunction() sarà non cambiare durante il ciclo.

+1

Vero. Ad esempio, fare una query a un database in 'someFunction()' sarà davvero una cattiva idea. Puoi facilmente evitarlo creando un numero intero prima del ciclo, assegnandogli un valore e quindi usando questo numero intero in un ciclo. –

3

Ci sono due modi per interpretare la tua domanda:

  • è la moltiplicazione dei 12332 * 324234 valutata su ogni loop
  • è l'espressione della condizione del ciclo valutata su ogni loop

Il rispondere a queste due domande diverse:

  • No, è in effetti valutato al comp ile-tempo, dal momento che si tratta di due costanti
  • Sì, se necessario, sono

In altre parole:

for (int i = 0; i < someString.Length; i++) 

Se la valutazione di someString.Length è costoso, ci sará una penale su ogni iterazione del ciclo.

+0

Dubito che String.Length funzioni come strlen. Tuttavia, "someString" potrebbe cambiare ad ogni ciclo di iterazione. – Luca

7

In realtà questo non verrà compilato perché verrà traboccato ma se lo fai un numero più piccolo e apri Reflector troverai qualcosa di simile.

for (int i = 0; i < 0x3cf7b0; i++) 
{ 

} 
+1

Buon punto sull'overflow – KLee1

0

Come segnalato da @Chaos, non verrà compilato. Ma se usi un'espressione rappresentabile (come 100 * 100), il risultato sarà probabilmente codificato. Sul Mono, il CIL comprende:

IL_0007: ldloc.0 
IL_0008: ldc.i4.1 
IL_0009: add 
IL_000a: stloc.0 
IL_000b: ldloc.0 
IL_000c: ldc.i4 10000 
IL_0011: blt IL_0007 

Come si può vedere, 100 * 100 è codificato come 10000. Tuttavia, in generale sarà valutata di volta in volta, e se un metodo o una proprietà si chiama, questo probabilmente non può essere ottimizzato.

0

Sembra essere calcolato ogni volta. Smontaggio da VS2008.

0000003b nop    
      for (Int64 i = 0; i < (Int64)12332 * (Int64)324234; i++) 
0000003c mov   qword ptr [rsp+20h],0 
00000045 jmp   000000000000005E 
      { 
00000047 nop    
       bool h = false; 
00000048 mov   byte ptr [rsp+28h],0 
      } 
0000004d nop    
      for (Int64 i = 0; i < (Int64)12332 * (Int64)324234; i++) 
0000004e mov   rax,qword ptr [rsp+20h] 
00000053 add   rax,1 
00000059 mov   qword ptr [rsp+20h],rax 
0000005e xor   ecx,ecx 
00000060 mov   eax,0EE538FB8h 
00000065 cmp   qword ptr [rsp+20h],rax 
0000006a setl  cl 
0000006d mov   dword ptr [rsp+2Ch],ecx 
00000071 movzx  eax,byte ptr [rsp+2Ch] 
00000076 mov   byte ptr [rsp+29h],al 
0000007a movzx  eax,byte ptr [rsp+29h] 
0000007f test  eax,eax 
00000081 jne   0000000000000047 
+1

Non c'è alcun comando di moltiplicazione nello smontaggio. –

13

Questo è uno dei comportamenti più comunemente frainteso di loop in C#.

Ecco cosa è necessario sapere:

Loop Bounds calcoli, se non costante e coinvolge una variabile, proprietà di accesso, chiamata di funzione, o delegare invocazione si ri-calcolo il valore dei limiti prima di ogni iterazione del ciclo .

Così, ad esempio:

for(int i = 0; i < 1234*1234; i++) { ... } 

In questo caso, l'espressione 1234*1234 è una fase di compilazione costante, e di conseguenza sarà non essere ricalcolata ad ogni iterazione. Infatti, viene calcolato al momento della compilazione e sostituito con una costante.

Tuttavia, in questo caso:

int k = 10; 
for(int i = 0; i < k; i++) { k -= 1; ... } 

Il valore di k deve essere esaminato ogni iterazione. Dopo tutto può cambiare .. in questo esempio. Fortunatamente, dal momento che k è semplicemente una variabile locale, il costo di accesso è molto basso - e in molti casi verrà conservato nella cache della CPU locale o forse verrà mantenuto in un registro (a seconda di come il JIT elabora ed emette il codice macchina).

Nel caso di qualcosa di simile al seguente:

IEnumerable<int> sequence = ...; 
for(int i = 0; i < sequence.Count(); i++) { ... } 

Il costo di calcolare il sequence.Count() può essere molto costoso. E poiché viene valutato su ogni iterazione del ciclo, può sommarsi rapidamente.

Il compilatore non può ottimizzare via le chiamate ai metodi o proprietà che si verificano all'interno dell'espressione limiti ciclo perché possono anche cambiare con ogni iterazione. Immaginate se il loop di cui sopra è stato scritto come:

IEnumerable<int> sequence = ...; 
for(int i = 0; i < sequence.Count(); i++) { 
    sequence = sequence.Concat(anotherItem); 
} 

Chiaramente sequence sta cambiando ad ogni iterazione ... e quindi la Count() è probabile che sia diverso su ogni iterazione. Il compilatore non tenta di eseguire alcune analisi statiche per determinare se l'espressione del limite di loop potrebbe essere costante ... che sarebbe estremamente complicata, se non impossibile. Invece, presuppone che se l'espressione non è una costante deve essere valutata su ogni iterazione.

Ora, nella maggior parte dei casi, il costo del calcolo del vincolo dei limiti per un ciclo sarà relativamente poco costoso, quindi non devi preoccuparti di questo. Ma devi capire come il compilatore tratta i limiti del ciclo come questo. Inoltre, come sviluppatore devi stare attento all'utilizzo di proprietà o metodi che hanno effetti collaterali come parte di un'espressione limite - dopo tutto, questi effetti collaterali si verificheranno su ogni iterazione del ciclo.

-2

Sì, il valore di confronto viene calcolato ogni ciclo di ciclo.

Se è assolutamente necessario utilizzare per-loop, che raramente è davvero necessario più, per quanto ne so c'è solo un "buon" modello di ciclo:

for (int i = first(), last = last(); i != last; ++i) 
{ 
// body 
} 

Avviso l'incremento del prefisso troppo.

+2

Questo è fuorviato per molte ragioni. Non è una questione di essere "necessario". Perché i loop non sono mai necessari, ma a volte c'è il modo più pulito per esprimere qualcosa. Non tutte le condizioni usano un metodo. In effetti, quello nella domanda no. Di quelli che lo fanno, a volte il metodo * deve * essere chiamato ogni volta, e in altri casi, la differenza di prestazioni è trascurabile. Infine, pre e post-incremento non fanno assolutamente nessuna differenza pratica qui. –

+0

Sono abbastanza sicuro che lo sviluppatore che utilizza questo modello sappia quando il metodo deve essere chiamato ogni volta e lo aggiusterà. Comunque, è un buon stile usare "for pattern" che è stato introdotto in C++ da Scott Meyers ed è comunque molto utile. – Grozz

+0

'++ i' e' i ++ 'non fanno alcuna differenza perché il compilatore li ottimizzerà per essere semanticamente identico. – KLee1

2

Prima di tutto il ciclo for nella domanda non verrà compilato. Ma diciamo che era

  for (int i = 0; i < 20; i++) 
     { 
      Console.WriteLine(i); 
      i++; 

     } 

VS

  for (int i = 0; i < 10*2; i++) 
     { 
      Console.WriteLine(i); 
      i++; 

     } 

il codice IL è esattamente lo stesso.

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    .maxstack 2 
    .locals init (
     [0] int32 i, 
     [1] bool CS$4$0000) 
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: br.s L_0016 
    L_0005: nop 
    L_0006: ldloc.0 
    L_0007: call void [mscorlib]System.Console::WriteLine(int32) 
    L_000c: nop 
    L_000d: ldloc.0 
    L_000e: ldc.i4.1 
    L_000f: add 
    L_0010: stloc.0 
    L_0011: nop 
    L_0012: ldloc.0 
    L_0013: ldc.i4.1 
    L_0014: add 
    L_0015: stloc.0 
    L_0016: ldloc.0 
    L_0017: ldc.i4.s 20 
    L_0019: clt 
    L_001b: stloc.1 
    L_001c: ldloc.1 
    L_001d: brtrue.s L_0005 
    L_001f: call int32 [mscorlib]System.Console::Read() 
    L_0024: pop 
    L_0025: ret 
} 

Ora, anche se sostituisco con una funzione nel ciclo come

class Program 
{ 
    static void Main(string[] args) 
    { 
     for (int i = 0; i < Foo(); i++) 
     { 
      Console.WriteLine(i); 
      i++; 

     } 
     Console.Read(); 
    } 

    private static int Foo() 
    { 
     return 20; 
    } 

ottengo questo codice IL

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    .maxstack 2 
    .locals init (
     [0] int32 i, 
     [1] bool CS$4$0000) 
    L_0000: nop 
    L_0001: ldc.i4.0 
    L_0002: stloc.0 
    L_0003: br.s L_0016 
    L_0005: nop 
    L_0006: ldloc.0 
    L_0007: call void [mscorlib]System.Console::WriteLine(int32) 
    L_000c: nop 
    L_000d: ldloc.0 
    L_000e: ldc.i4.1 
    L_000f: add 
    L_0010: stloc.0 
    L_0011: nop 
    L_0012: ldloc.0 
    L_0013: ldc.i4.1 
    L_0014: add 
    L_0015: stloc.0 
    L_0016: ldloc.0 
    L_0017: call int32 TestBedForums.Program::Foo() 
    L_001c: clt 
    L_001e: stloc.1 
    L_001f: ldloc.1 
    L_0020: brtrue.s L_0005 
    L_0022: call int32 [mscorlib]System.Console::Read() 
    L_0027: pop 
    L_0028: ret 
} 

che sembra lo stesso per me.

Quindi per me sembra che non ci siano differenze in FOR LOOP con limite finito, un altro con limite di calcolo e ultimo con limite proveniente da una funzione.

Quindi finché il codice viene copiato, sai cosa stai facendo in un loop mastodontico come questo e hai abbastanza memoria in corso penso che funzionerà e produrrà lo stesso perf. (in C#)