Quel extrait de code donnera de meilleures performances ? Le code ci-dessous les segments ont été écrit en c#.
1.
2.
Quel extrait de code donnera de meilleures performances ? Le code ci-dessous les segments ont été écrit en c#.
1.
2.
Eh bien, cela dépend en partie du type exact de l' list
. Elle dépendra aussi de l'exacte CLR que vous utilisez.
Si c'est de toute façon significative ou non dépendra de savoir si vous êtes en train de faire tout travail réel dans la boucle. Dans presque tous les cas, la différence de performance ne sera pas significative, mais la différence de lisibilité favorise l' foreach
boucle.
Je serais personnellement utiliser LINQ pour éviter le "si":
foreach (var item in list.Where(condition))
{
}
EDIT: Pour ceux d'entre vous qui prétendez qu'une itération sur un List<T>
avec foreach
produit le même code que l' for
boucle, voici la preuve qu'il n'a pas:
static void IterateOverList(List<object> list)
{
foreach (object o in list)
{
Console.WriteLine(o);
}
}
Produit de IL de:
.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
Le compilateur traite des tableaux différemment, la conversion d'un foreach
boucle essentiellement à un for
boucle, mais pas List<T>
. Voici le code équivalent d'un tableau:
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
Il est intéressant de noter, je ne trouve pas cette documenté dans le C# 3 spec n'importe où...
Un for
boucle est compilé en code approximativement équivalent à ceci:
int tempCount = 0;
while (tempCount < list.Count)
{
if (list[tempCount].value == value)
{
// Do something
}
tempCount++;
}
Lorsque, en foreach
boucle est compilé en code approximativement équivalent à ceci:
using (IEnumerator<T> e = list.GetEnumerator())
{
while (e.MoveNext())
{
T o = (MyClass)e.Current;
if (row.value == value)
{
// Do something
}
}
}
Donc, comme vous pouvez le voir, il serait tout dépend de la façon dont l'agent recenseur est mis en œuvre par rapport à ce que les listes de l'indexeur est mis en œuvre. Il s'avère que l'agent recenseur pour des types de tableaux sont normalement écrit quelque chose comme ceci:
private static IEnumerable<T> MyEnum(List<T> list)
{
for (int i = 0; i < list.Count; i++)
{
yield return list[i];
}
}
Donc, comme vous pouvez le voir, dans ce cas, il ne fera pas beaucoup de différence, toutefois, l'agent recenseur pour une liste liée serait probablement ressembler à quelque chose comme ceci:
private static IEnumerable<T> MyEnum(LinkedList<T> list)
{
LinkedListNode<T> current = list.First;
do
{
yield return current.Value;
current = current.Next;
}
while (current != null);
}
Dans .NET , vous trouverez que la LinkedList<T> la classe ne dispose même pas d'un indexeur, de sorte que vous ne seriez pas en mesure de faire votre pour la boucle sur une liste liée; mais si vous pouviez, l'indexeur devra être écrite comme suit:
public T this[int index]
{
LinkedListNode<T> current = this.First;
for (int i = 1; i <= index; i++)
{
current = current.Next;
}
return current.value;
}
Comme vous pouvez le voir, l'appeler plusieurs fois dans une boucle va être beaucoup plus lent que d'utiliser un énumérateur qui peut se rappeler où il est dans la liste.
Un simple test pour semi-valider. J'ai fait un petit test, juste pour voir. Voici le 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();
}
Et voici le foreach section:
foreach (int i in intList)
{
int foo = i * 2;
if (foo % 2 == 0)
{
}
}
Quand j'ai remplacé le pour avec un foreach -- le foreach est de 20 millisecondes plus vite - systématiquement. Le pour a été de 135 139ms tandis que le foreach était de 113-119ms. J'ai échangé avant en arrière plusieurs fois, en s'assurant qu'il n'était pas un processus qui vient de débuter.
Cependant, quand j'ai enlevé le foo et de l'instruction if, le pour a été plus rapide en 30 ms (foreach était 88ms et pour a 59 ms). Ils étaient tous les deux des coquilles vides. Je suis en supposant que le foreach effectivement adopté une variable où, comme l'était juste incrémentation d'une variable. Si j'ai ajouté
int foo = intList[i];
Alors la pour devenir lent d'environ 30ms. Je suppose que cela a à voir avec elle la création de foo et l'accaparement de la variable dans le tableau et en l'assignant à toto. Si vous venez de l'accès intList[i] alors vous n'avez pas la pénalité.
En toute honnêteté.. je m'attendais à la boucle foreach pour un peu plus de temps, en toutes circonstances, mais pas assez de matière dans la plupart des applications.
edit: voici le nouveau code à l'aide de Jons suggestions (134217728 est le plus int vous pouvez avoir avant du Système.OutOfMemory exception est lancée):
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();
}
Et voici les résultats:
La production de données. Le calcul pour la boucle: 2458ms Calcul de la boucle foreach: 2005ms
Échangeant autour pour voir si elle traite avec l'ordre des choses donne les mêmes résultats (ou presque).
Note: cette réponse s'applique plus à Java qu'en C#, depuis C# n'est pas un indexeur sur LinkedLists
, mais je pense que le point de vue général tient toujours.
Si l' list
vous travaillez avec un LinkedList
, le rendement de l'indexeur-code (tableau de style , pour y accéder est bien pire qu'à l'aide de l' IEnumerator
de la foreach
, pour les grandes listes.
Lorsque vous accédez à l'élément de 10.000 dans un LinkedList
l'utilisation de l'indexeur syntaxe: list[10000]
, la liste liée débutera à la tête de nœud, et traverse l' Next
-pointeur dix mille fois, jusqu'à ce qu'il atteigne le bon objet. Évidemment, si vous le faites dans une boucle, vous obtiendrez:
list[0]; // head
list[1]; // head.Next
list[2]; // head.Next.Next
// etc.
Lorsque vous appelez GetEnumerator
(implicitement à l'aide de l' forach
-syntaxe), vous aurez une IEnumerator
objet qui a un pointeur vers le nœud de tête. Chaque fois que vous appelez MoveNext
, ce pointeur est déplacé vers le nœud suivant, comme suit:
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.
Comme vous pouvez le voir, dans le cas d' LinkedList
s, le tableau de l'indexeur méthode devient plus lent et plus lent, plus vous en boucle (il doit passer par le même pointeur de tête, encore et encore). Alors que l' IEnumerable
seulement fonctionne en temps constant.
Bien sûr, comme Jon a dit cela dépend vraiment du type d' list
, si l' list
n'est pas un LinkedList
, mais un tableau, le comportement est complètement différent.
Comme d'autres personnes l'ont mentionné, bien que la performance n'a pas vraiment d'importance, le foreach sera toujours un peu plus lent en raison de l' IEnumerable
/IEnumerator
d'utilisation dans la boucle. Le compilateur traduit le construire dans les appels sur cette interface, et pour chaque étape une fonction + une propriété sont appelés, dans l'instruction foreach.
IEnumerator iterator = ((IEnumerable)list).GetEnumerator();
while (iterator.MoveNext()) {
var item = iterator.Current;
// do stuff
}
C'est l'équivalent de l'expansion de la construction en C#. Vous pouvez imaginer l'impact sur les performances peuvent varier en fonction de la mise en œuvre de la méthode MoveNext et Actuel. Alors que dans un tableau, vous n'avez pas que des dépendances.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.