Voici un article qui traite d’optimisation sur les traitements de données. Je vais vous présenter une technique qui n’est pas du tout évidente à première vue.
Habituellement, lorsque vous analysez des données, vous utilisez des listes ou des tableaux qui implémentent ICollection ou IEnumerable. Ces interfaces sont intéressantes car elles permettent d’utiliser des fonctionnalités puissantes dans .NET :
- Si vous utilisez ICollection, vous pourrez accéder à la propriété Count pour connaître le nombre d’éléments dans la liste.
- Si vous utilisez IEnumerable, vous pourrez utiliser l’opérateur foreach pour parcourir un ensemble d’éléments.
- ICollection hérite de IEnumerable, donc toutes les collections peuvent être utilisées avec foreach.
L’objectif de cet article est de vous parler d’optimisation de code afin d’améliorer la vitesse d’exécution de votre application.
Pour vérifier si une liste est vide, je suppose que vous écrivez habituellement du code qui ressemble à ceci :
[csharp]
if (maListe.Count == 0) {
/* traiter le cas de la liste vide */
}
[/csharp]
Récemment, en écrivant ce code, je me suis posé la question suivante : est-ce que Count est calculé à chaque fois ou bien est-ce que c’est une valeur qui est stockée ?
Comme expliqué précédemment, les types ICollection proposent une propriété Count qui stocke le nombre d’éléments. Donc, la valeur est connue.
Le problème vient très souvent de l’utilisation de la méthode d’extension Count() proposée par Linq.
La méthode Count() peut être utilisée sur des types plus larges comme les IEnumerables.
C’est là que peut survenir un problème de performance important :
- Les types ICollection stockent le nombre d’éléments en interne. L’appel de .Count retourne ce nombre rapidement.
- L’utilisation des méthodes d’extension Count() effectuent un calcul à chaque appel. Si vous avez un million d’éléments dans la liste, il faudra patienter un peu pour avoir le résultat.
Heureusement voilà, il existe une solution simple pour régler ce problème.
Linq propose également une méthode d’extension Any() qui retourne true si un IEnumerable contient au moins un élément.
Any() effectue une itération pour vérifier s’il y a au moins un élément dans un ensemble.
Any() est plus performant que de compter le nombre d’éléments puis de vérifier s’il est supérieur à zéro (Count > 0).
Any() semble donc être la solution. C’est aussi ce que je pensais jusqu’à ce que je regarde comment ça fonctionne « sous le capot » (grâce à la décompilation).
En réalité, .NET effectue une optimisation, on peut le voir dans la version décompilée :
[csharp]
public static int Count<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
throw Error.ArgumentNull("source");
ICollection<TSource> collection1 = source as ICollection<TSource>;
if (collection1 != null)
return collection1.Count;
ICollection collection2 = source as ICollection;
if (collection2 != null)
return collection2.Count;
int num = 0;
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
checked { ++num; }
}
return num;
}
[/csharp]
- L’appel de Count vérifie si le type est compatible avec ICollection. Si oui, alors le framework appelle ICollection.Count, sinon il effectue une boucle pour calculer le nombre d’éléments.
Bilan
Je souhaitais simplement retenir votre attention sur les éléments suivants :
- La méthode Any() existe et il est possible de l’utiliser. Si vous souhaitez savoir si un ensemble comporte au moins un élément, utilisez-là, elle est faite pour ça.
- Pour des algorithmes qui utilisent ou vérifient le nombre d’éléments, préférez des types ICollection, le code sera toujours plus efficace.
- Utilisez .Count ou Count() sur vos ensembles, sauf si vous êtes certain que le type est uniquement IEnumerable (et dans ce cas, utilisez Any() ).
Et vous, avez-vous des optimisations qui ressemblent à celles-ci ?
Bien intéressant cet article, en particulier la partie sur l’optimisation réalisée sur l’utilisation du Count sur une ICollection.
Les utilisateurs de Resharper le savent, celui-ci recommande lui aussi l’utilisation de Any() sur un IEnumerable.
Merci pour cette contribution.
Effectivement, Resharper propose d’utiliser Any(). Et justement, là aussi il faut se demander si la liste d’éléments peut être du type ICollection ou pas.
Bonjour,
Je ne suis pas d’accord du tout avec la conclusion de cet article. Les méthodes d’extension Any() et Count() de IEnumerable portent parfaitement leur noms : l’une est faite pour savoir si une séquence contient au moins un élément, l’autre pour connaitre le nombre d’éléments d’une séquence.
Utiliser Count() pour savoir si une séquence contient au moins un élément, sous prétexte que l’on « sait » que l’IEnumerable est en réalité une Collection, est à mon sens un très mauvaise idée. Le gain de performance associé, s’il existe, doit être extrêmement petit. Dans toutes les situations, Any() reste un appel très peu coûteux, alors que Count() peut être très coûteux lorsque l’IEnumerable n’est pas une collection.
« Mais puisque l’on sait que c’est une Collection », me direz-vous… Alors pourquoi est-ce que je manipule un IEnumerable et pas une collection alors ?
Merci Pierre de contribuer à la discussion.
En fait, je pense que ma conclusion n’était pas assez claire. Je vais corriger ça.
C’est certain que si on est sûr du type, il vaut mieux utiliser la bonne méthode d’extension.
Je souhaitais simplement insister sur le fait que beaucoup de développeurs ne connaissent pas Any() et utilisent donc toujours « if Count> 0… ».