Welk codefragment levert betere prestaties? De onderstaande codesegmenten zijn geschreven in C#.
1.
for(int tempCount=0;tempCount<list.count;tempcount++)
{
if(list[tempCount].value==value)
{
// Some code.
}
}
foreach(object row in list)
{
if(row.value==value)
{
//Some coding
}
}
Antwoord 1, autoriteit 100%
Nou, het hangt gedeeltelijk af van het exacte type list
. Het hangt ook af van de exacte CLR die u gebruikt.
Of het op enigerlei wijze aanzienlijkis of niet, hangt ervan af of je in de loop van de tijd echt werk doet. In bijna allegevallen zal het verschil in prestatie niet significant zijn, maar het verschil in leesbaarheid is gunstig voor de foreach
-lus.
Persoonlijk zou ik LINQ gebruiken om de “als” ook te vermijden:
foreach (var item in list.Where(condition))
{
}
EDIT: voor degenen onder u die beweren dat het herhalen van een List<T>
met foreach
dezelfde code oplevert als de for
loop, hier is bewijs dat dit niet het geval is:
static void IterateOverList(List<object> list)
{
foreach (object o in list)
{
Console.WriteLine(o);
}
}
Produceert IL van:
.method private hidebysig static void IterateOverList(class [mscorlib]System.Collections.Generic.List`1<object> list) cil managed
{
// Code size 49 (0x31)
.maxstack 1
.locals init (object V_0,
valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object> V_1)
IL_0000: ldarg.0
IL_0001: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<object>::GetEnumerator()
IL_0006: stloc.1
.try
{
IL_0007: br.s IL_0017
IL_0009: ldloca.s V_1
IL_000b: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::get_Current()
IL_0010: stloc.0
IL_0011: ldloc.0
IL_0012: call void [mscorlib]System.Console::WriteLine(object)
IL_0017: ldloca.s V_1
IL_0019: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::MoveNext()
IL_001e: brtrue.s IL_0009
IL_0020: leave.s IL_0030
} // end .try
finally
{
IL_0022: ldloca.s V_1
IL_0024: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>
IL_002a: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_002f: endfinally
} // end handler
IL_0030: ret
} // end of method Test::IterateOverList
De compiler behandelt arraysanders, en converteert een foreach
-lus in principe naar een for
-lus, maar niet List<T>
. Hier is de equivalente code voor een array:
static void IterateOverArray(object[] array)
{
foreach (object o in array)
{
Console.WriteLine(o);
}
}
// Compiles into...
.method private hidebysig static void IterateOverArray(object[] 'array') cil managed
{
// Code size 27 (0x1b)
.maxstack 2
.locals init (object V_0,
object[] V_1,
int32 V_2)
IL_0000: ldarg.0
IL_0001: stloc.1
IL_0002: ldc.i4.0
IL_0003: stloc.2
IL_0004: br.s IL_0014
IL_0006: ldloc.1
IL_0007: ldloc.2
IL_0008: ldelem.ref
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: call void [mscorlib]System.Console::WriteLine(object)
IL_0010: ldloc.2
IL_0011: ldc.i4.1
IL_0012: add
IL_0013: stloc.2
IL_0014: ldloc.2
IL_0015: ldloc.1
IL_0016: ldlen
IL_0017: conv.i4
IL_0018: blt.s IL_0006
IL_001a: ret
} // end of method Test::IterateOverArray
Interessant genoeg kan ik dit nergens in de C# 3-specificatie vinden…
Antwoord 2, autoriteit 11%
Een for
-lus wordt gecompileerd tot code die ongeveer gelijk is aan deze:
int tempCount = 0;
while (tempCount < list.Count)
{
if (list[tempCount].value == value)
{
// Do something
}
tempCount++;
}
Waar als een foreach
-lus wordt gecompileerd tot code die ongeveer gelijk is aan deze:
using (IEnumerator<T> e = list.GetEnumerator())
{
while (e.MoveNext())
{
T o = (MyClass)e.Current;
if (row.value == value)
{
// Do something
}
}
}
Zoals je kunt zien, hangt het er allemaal van af hoe de enumerator is geïmplementeerd en hoe de lijsten-indexer is geïmplementeerd. Het blijkt dat de enumerator voor typen op basis van arrays normaal gesproken ongeveer als volgt wordt geschreven:
private static IEnumerable<T> MyEnum(List<T> list)
{
for (int i = 0; i < list.Count; i++)
{
yield return list[i];
}
}
Zoals je kunt zien, maakt het in dit geval niet veel uit, maar de enumerator voor een gekoppelde lijst ziet er waarschijnlijk ongeveer zo uit:
private static IEnumerable<T> MyEnum(LinkedList<T> list)
{
LinkedListNode<T> current = list.First;
do
{
yield return current.Value;
current = current.Next;
}
while (current != null);
}
In .NETvindt u dat de LinkedList<T> class heeft niet eens een indexer, dus je zou je for-lus niet kunnen doen op een gelinkte lijst; maar als je dat zou kunnen, zou de indexer zo moeten worden geschreven:
public T this[int index]
{
LinkedListNode<T> current = this.First;
for (int i = 1; i <= index; i++)
{
current = current.Next;
}
return current.value;
}
Zoals je kunt zien, zal het meerdere keren in een lus aanroepen veel langzamer zijn dan het gebruik van een enumerator die kan onthouden waar het zich in de lijst bevindt.
Antwoord 3, autoriteit 9%
Een eenvoudige test om semi-valideren. Ik heb een kleine test gedaan, gewoon om te zien. Hier is de code:
static void Main(string[] args)
{
List<int> intList = new List<int>();
for (int i = 0; i < 10000000; i++)
{
intList.Add(i);
}
DateTime timeStarted = DateTime.Now;
for (int i = 0; i < intList.Count; i++)
{
int foo = intList[i] * 2;
if (foo % 2 == 0)
{
}
}
TimeSpan finished = DateTime.Now - timeStarted;
Console.WriteLine(finished.TotalMilliseconds.ToString());
Console.Read();
}
En hier is de voorpagina:
foreach (int i in intList)
{
int foo = i * 2;
if (foo % 2 == 0)
{
}
}
Toen ik de for verving door een foreach — de foreach was 20 milliseconden sneller — consistent. De for was 135-139 ms, terwijl de foreach 113-119 ms was. Ik heb verschillende keren heen en weer gewisseld, om er zeker van te zijn dat het niet een of ander proces was dat net begon.
Toen ik echter de foo en de if-statement verwijderde, was de for 30 ms sneller (foreach was 88 ms en for was 59 ms). Het waren allebei lege hulzen. Ik ga ervan uit dat de foreach daadwerkelijk een variabele heeft doorgegeven, terwijl de for gewoon een variabele verhoogde. Als ik heb toegevoegd
int foo = intList[i];
Vervolgens wordt het ongeveer 30 ms traag. Ik neem aan dat dit te maken had met het maken van foo en het pakken van de variabele in de array en het toewijzen aan foo. Als je alleen intList[i] opent, heb je die straf niet.
Eerlijk gezegd.. Ik had verwacht dat de foreach onder alle omstandigheden iets langzamer zou zijn, maar niet genoeg om in de meeste toepassingen van belang te zijn.
edit: hier is de nieuwe code die Jons-suggesties gebruikt (134217728 is de grootste int die je kunt hebben voordat de uitzondering System.OutOfMemory wordt gegenereerd):
static void Main(string[] args)
{
List<int> intList = new List<int>();
Console.WriteLine("Generating data.");
for (int i = 0; i < 134217728 ; i++)
{
intList.Add(i);
}
Console.Write("Calculating for loop:\t\t");
Stopwatch time = new Stopwatch();
time.Start();
for (int i = 0; i < intList.Count; i++)
{
int foo = intList[i] * 2;
if (foo % 2 == 0)
{
}
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms");
Console.Write("Calculating foreach loop:\t");
time.Reset();
time.Start();
foreach (int i in intList)
{
int foo = i * 2;
if (foo % 2 == 0)
{
}
}
time.Stop();
Console.WriteLine(time.ElapsedMilliseconds.ToString() + "ms");
Console.Read();
}
En hier zijn de resultaten:
Gegevens genereren.
Berekenen voor lus: 2458ms
Foreach-lus berekenen: 2005ms
Als je ze omwisselt om te zien of het omgaat met de volgorde van de dingen, krijg je (bijna) dezelfde resultaten.
Antwoord 4, autoriteit 7%
Opmerking: dit antwoord is meer van toepassing op Java dan op C#, aangezien C# geen indexer heeft op LinkedLists
, maar ik denk dat het algemene punt nog steeds geldt.
Als de list
waarmee u werkt een LinkedList
is, kan de prestatie van de indexer-code (array-stijldie toegang ) is veel erger dan het gebruik van de IEnumerator
van de foreach
, voor grote lijsten.
Wanneer u element 10.000 in een LinkedList
opent met behulp van de indexersyntaxis: list[10000]
, begint de gekoppelde lijst bij het hoofdknooppunt en doorloopt de Next
-pointer tienduizend keer, totdat het het juiste object bereikt. Als je dit in een lus doet, krijg je uiteraard:
list[0]; // head
list[1]; // head.Next
list[2]; // head.Next.Next
// etc.
Wanneer u GetEnumerator
aanroept (impliciet met behulp van de forach
-syntaxis), krijgt u een IEnumerator
-object met een verwijzing naar de hoofd knooppunt. Elke keer dat u MoveNext
aanroept, wordt die aanwijzer verplaatst naar het volgende knooppunt, zoals:
IEnumerator em = list.GetEnumerator(); // Current points at head
em.MoveNext(); // Update Current to .Next
em.MoveNext(); // Update Current to .Next
em.MoveNext(); // Update Current to .Next
// etc.
Zoals je kunt zien, wordt in het geval van LinkedList
s de array-indexermethode langzamer en langzamer, hoe langer je lus (hij moet steeds opnieuw door dezelfde hoofdaanwijzer gaan) . Terwijl de IEnumerable
gewoon in constante tijd werkt.
Natuurlijk, zoals Jon zei, hangt dit echt af van het type list
, als de list
geen LinkedList
is, maar een array , het gedrag is compleet anders.
Antwoord 5, autoriteit 2%
Zoals andere mensen al hebben gezegd, hoewel de prestaties er niet echt toe doen, zal de foreach altijd een beetje langzamer zijn vanwege het gebruik van IEnumerable
/IEnumerator
in de lus. De compiler vertaalt de constructie naar aanroepen op die interface en voor elke stap wordt een functie + een eigenschap aangeroepen in de foreach-constructie.
IEnumerator iterator = ((IEnumerable)list).GetEnumerator();
while (iterator.MoveNext()) {
var item = iterator.Current;
// do stuff
}
Dit is de equivalente expansie van het construct in C #. Je kunt je voorstellen hoe de prestatie-impact kan variëren op basis van de implementaties van Moventekst en Stroom. Overwegende dat u in een array-toegang die afhankelijkheden niet hebt.
6
Er is een ander interessant feit dat gemakkelijk kan worden gemist bij het testen van de snelheid van beide lussen:
Met behulp van de debug-modus laat de compiler de code niet optimaliseren met behulp van de standaardinstellingen.
Dit leidde me naar het interessante resultaat dat het foreach sneller is dan voor in de debug-modus. Overwegende dat het voor het is sneller is dan foreach in de releasemodus. Uiteraard heeft de compiler betere manieren om een voor lus te optimaliseren dan een foreach-lus die verschillende methode-oproepen in gevaar brengt. A voor lus is trouwens zo fundamenteel dat dit mogelijk is dat dit zelfs wordt geoptimaliseerd door de CPU zelf.
7
In het voorbeeld dat u hebt opgegeven, is het zeker beter om foreach
lus in plaats daarvan een for
lus te gebruiken.
De standaard foreach
Construct kan sneller zijn (1,5 cycli per stap) dan een eenvoudige for-loop
(2 cycli per stap), tenzij de lus is geweest Uitgerold (1,0 cycli per stap).
Dus voor dagelijkse code, prestaties zijn geen reden om de complexere for
, while
of do-while
constructen.
Bekijk deze link: http: / /www.codeproject.com/articles/146797/fast-and-less-fast-loops-in-c
╔══════════════════════╦═══════════╦═══════╦════════════════════════╦═════════════════════╗
║ Method ║ List<int> ║ int[] ║ Ilist<int> onList<Int> ║ Ilist<int> on int[] ║
╠══════════════════════╬═══════════╬═══════╬════════════════════════╬═════════════════════╣
║ Time (ms) ║ 23,80 ║ 17,56 ║ 92,33 ║ 86,90 ║
║ Transfer rate (GB/s) ║ 2,82 ║ 3,82 ║ 0,73 ║ 0,77 ║
║ % Max ║ 25,2% ║ 34,1% ║ 6,5% ║ 6,9% ║
║ Cycles / read ║ 3,97 ║ 2,93 ║ 15,41 ║ 14,50 ║
║ Reads / iteration ║ 16 ║ 16 ║ 16 ║ 16 ║
║ Cycles / iteration ║ 63,5 ║ 46,9 ║ 246,5 ║ 232,0 ║
╚══════════════════════╩═══════════╩═══════╩════════════════════════╩═════════════════════╝
8
U kunt erover lezen in Diepe .NET – Deel 1 iteratie
Het is betrekking op de resultaten (zonder de eerste initialisatie) van .NET broncode helemaal naar de demontage.
Bijvoorbeeld – array iteratie met een foreach-lus: