De discussie over het voorkeursverschil tussen FOREACH en FOR is niet nieuw. We weten allemaal dat FOREACH langzamer is, maar we weten niet allemaal waarom.
Toen ik begon met het leren van .NET, vertelde een persoon me dat FOREACH twee keer langzamer is dan FOR. Hij zei dit zonder enige grond. Ik nam het als vanzelfsprekend aan.
Uiteindelijk besloot ik om het prestatieverschil van FOREACH en FOR-lus te onderzoeken en dit artikel te schrijven om nuances te bespreken.
Laten we eens kijken naar de volgende code:
foreach (var item in Enumerable.Range(0, 128))
{
Console.WriteLine(item);
}
De FOREACH is een syntaxissuiker. In dit specifieke geval transformeert de compiler het in de volgende code:
IEnumerator<int> enumerator = Enumerable.Range(0, 128).GetEnumerator();
try
{
while (enumerator.MoveNext())
{
int item = enumerator.Current;
Console.WriteLine(item);
}
}
finally
{
if (enumerator != null)
{
enumerator.Dispose();
}
}
Als we dit weten, kunnen we de reden aannemen waarom FOREACH langzamer is dan FOR:
- Er wordt een nieuw object gemaakt. Het heet Creator.
- De MoveNext-methode wordt bij elke iteratie aangeroepen.
- Elke iteratie geeft toegang tot de eigenschap Current.
Dat is het! Het is echter niet allemaal zo eenvoudig als het klinkt.
Gelukkig (of helaas) kan C#/CLR tijdens runtime optimalisaties uitvoeren. De pro is dat de code sneller werkt. Het nadeel - ontwikkelaars moeten op de hoogte zijn van deze optimalisaties.
De array is een type dat diep in CLR is geïntegreerd en CLR biedt een aantal optimalisaties voor dit type. De FOREACH-lus is een itereerbare entiteit, wat een belangrijk aspect van de uitvoering is. Verderop in het artikel zullen we bespreken hoe u door arrays en lijsten kunt bladeren met behulp van de statische methode Array.ForEach en de methode List.ForEach.
Testmethoden
static double ArrayForWithoutOptimization(int[] array)
{
int sum = 0;
var watch = Stopwatch.StartNew();
for (int i = 0; i < array.Length; i++)
sum += array[i];
watch.Stop();
return watch.Elapsed.TotalMilliseconds;
}
static double ArrayForWithOptimization(int[] array)
{
int length = array.Length;
int sum = 0;
var watch = Stopwatch.StartNew();
for (int i = 0; i < length; i++)
sum += array[i];
watch.Stop();
return watch.Elapsed.TotalMilliseconds;
}
static double ArrayForeach(int[] array)
{
int sum = 0;
var watch = Stopwatch.StartNew();
foreach (var item in array)
sum += item;
watch.Stop();
return watch.Elapsed.TotalMilliseconds;
}
static double ArrayForEach(int[] array)
{
int sum = 0;
var watch = Stopwatch.StartNew();
Array.ForEach(array, i => { sum += i; });
watch.Stop();
return watch.Elapsed.TotalMilliseconds;
}
Testomstandigheden:
- De optie "Code optimaliseren" is ingeschakeld.
- Het aantal elementen is gelijk aan 100 000 000 (zowel in de array als in de lijst).
- PC-specificatie:Intel Core i-5 en 8 GB RAM.
Arrays
Het diagram laat zien dat FOR en FOREACH dezelfde hoeveelheid tijd besteden aan het doorlopen van arrays. En het is omdat CLR-optimalisatie FOREACH omzet in FOR en de lengte van de array gebruikt als de maximale iteratiegrens. Het maakt niet uit of de array-lengte in de cache is opgeslagen of niet (bij gebruik van FOR), het resultaat is bijna hetzelfde.
Het klinkt misschien vreemd, maar het cachen van de arraylengte kan de prestaties beïnvloeden. Tijdens het gebruik van array .Lengte als de iteratiegrens, JIT test de index om de rechtergrens voorbij de cyclus te raken. Deze controle wordt slechts één keer uitgevoerd.
Het is heel gemakkelijk om deze optimalisatie te vernietigen. Het geval dat de variabele in de cache wordt opgeslagen, is nauwelijks geoptimaliseerd.
Array.foreach liet de slechtste resultaten zien. De implementatie ervan is vrij eenvoudig:
public static void ForEach<T>(T[] array, Action<T> action)
{
for (int index = 0; index < array.Length; ++index)
action(array[index]);
}
Waarom gaat het dan zo traag? Het gebruikt FOR onder de motorkap. Nou, de reden is om de ACTIE-afgevaardigde te bellen. In feite wordt bij elke iteratie een methode aangeroepen, waardoor de prestaties afnemen. Bovendien worden de afgevaardigden niet zo snel aangeroepen als we zouden willen.
Lijsten
Het resultaat is totaal anders. Bij het herhalen van lijsten laten FOR en FOREACH verschillende resultaten zien. Er is geen optimalisatie. FOR (met caching van de lengte van de lijst) geeft het beste resultaat, terwijl FOREACH meer dan 2 keer langzamer is. Het is omdat het MoveNext en Current onder de motorkap behandelt. Zowel List.ForEach als Array.ForEach laten het slechtste resultaat zien. Afgevaardigden worden altijd virtueel gebeld. De implementatie van deze methode ziet er als volgt uit:
public void ForEach(Action<T> action)
{
int num = this._version;
for (int index = 0; index < this._size && num == this._version; ++index)
action(this._items[index]);
if (num == this._version)
return;
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
Elke iteratie roept de Action-delegate aan. Het controleert ook of de lijst is gewijzigd en zo ja, wordt er een uitzondering gegenereerd.
List gebruikt intern een array-gebaseerd model en de ForEach-methode gebruikt de array-index om door te itereren, wat aanzienlijk sneller is dan het gebruik van de indexer.
Specifieke nummers
- De FOR-lus zonder lengtecaching en FOREACH werken iets sneller op arrays dan FOR met lengtecaching.
- Array.Foreach prestatie is ongeveer 6 keer langzamer dan de prestaties van FOR / FOREACH.
- De FOR-lus zonder lengtecaching werkt 3 keer langzamer op lijsten, vergeleken met arrays.
- De FOR-lus met lengtecaching werkt 2 keer langzamer op lijsten, vergeleken met arrays.
- De FOREACH-lus werkt 6 keer langzamer op lijsten, vergeleken met arrays.
Hier is een klassement voor lijsten:
En voor arrays:
Conclusie
Ik heb echt genoten van dit onderzoek, vooral van het schrijfproces, en ik hoop dat jullie er ook van genoten hebben. Het bleek dat FOREACH sneller is op arrays dan FOR met het najagen van lengte. Op lijststructuren is FOREACH langzamer dan FOR.
De code ziet er beter uit bij het gebruik van FOREACH, en moderne processors maken het gebruik ervan mogelijk. Als u uw codebase echter sterk moet optimaliseren, is het beter om FOR te gebruiken.
Wat denk je, welke lus loopt sneller, FOR of FOREACH?