Quelqu'un pourrait-il m'expliquer pourquoi la List.Contains()
fonction générique est si lente?
J'ai un List<long>
avec environ un million de nombres, et le code qui vérifie constamment s'il y a un nombre spécifique dans ces nombres.
J'ai essayé de faire la même chose en utilisant Dictionary<long, byte>
et la Dictionary.ContainsKey()
fonction, et c'était environ 10 à 20 fois plus rapide qu'avec la liste.
Bien sûr, je ne veux pas vraiment utiliser Dictionary dans ce but, car il n'était pas destiné à être utilisé de cette façon.
Donc, la vraie question ici est, y a-t-il une alternative au List<T>.Contains()
, mais pas aussi farfelue que Dictionary<K,V>.ContainsKey()
?
Réponses:
Si vous ne faites que vérifier l'existence,
HashSet<T>
dans .NET 3.5 est votre meilleure option - des performances de type dictionnaire, mais pas de paire clé / valeur - juste les valeurs:la source
List.Contains est une opération O (n).
Dictionary.ContainsKey est une opération O (1), car elle utilise le hashcode des objets comme clé, ce qui vous donne une capacité de recherche plus rapide.
Je ne pense pas que ce soit une bonne idée d 'avoir une liste contenant un million d' entrées. Je ne pense pas que la classe List ait été conçue à cet effet. :)
N'est-il pas possible de sauvegarder ces entités millon dans un SGBDR par exemple, et d'effectuer des requêtes sur cette base de données?
Si ce n'est pas possible, j'utiliserais de toute façon un dictionnaire.
la source
Je pense avoir la réponse! Oui, il est vrai que Contains () sur une liste (tableau) est O (n), mais si le tableau est court et que vous utilisez des types valeur, cela devrait quand même être assez rapide. Mais en utilisant le CLR Profiler [téléchargement gratuit de Microsoft], j'ai découvert que Contains () boxe les valeurs afin de les comparer, ce qui nécessite une allocation de tas, ce qui est TRÈS cher (lent). [Remarque: il s'agit de .Net 2.0; autres versions .Net non testées.]
Voici l'histoire complète et la solution. Nous avons une énumération appelée "VI" et avons créé une classe appelée "ValueIdList" qui est un type abstrait pour une liste (tableau) d'objets VI. L'implémentation d'origine était dans les anciens jours de .Net 1.1, et elle utilisait une ArrayList encapsulée. Nous avons découvert récemment dans http://blogs.msdn.com/b/joshwil/archive/2004/04/13/112598.aspx qu'une liste générique (List <VI>) fonctionne bien mieux que ArrayList sur les types valeur (comme notre enum VI) car les valeurs n'ont pas à être encadrées. C'est vrai et ça a marché ... presque.
Le CLR Profiler a révélé une surprise. Voici une partie du graphique d'allocation:
Comme vous pouvez le voir, Contains () appelle de manière surprenante Generic.ObjectEqualityComparer.Equals (), qui nécessite apparemment l'encadrement d'une valeur de VI, ce qui nécessite une allocation de tas coûteuse. Il est étrange que Microsoft supprime la boxe de la liste, seulement pour l'exiger à nouveau pour une opération simple comme celle-ci.
Notre solution était de réécrire l'implémentation Contains (), ce qui dans notre cas était facile à faire puisque nous encapsulions déjà l'objet liste générique (_items). Voici le code simple:
La comparaison des valeurs de VI se fait maintenant dans notre propre version d'IndexOf () qui ne nécessite aucune boxe, et c'est très rapide. Notre programme particulier a accéléré de 20% après cette simple réécriture. O (n) ... pas de problème! Évitez simplement le gaspillage de la mémoire!
la source
Contains
implémentation personnalisée est bien plus rapide pour mon cas d'utilisation.Le dictionnaire n'est pas si mal, car les clés d'un dictionnaire sont conçues pour être trouvées rapidement. Pour trouver un numéro dans une liste, il doit parcourir toute la liste.
Bien sûr, le dictionnaire ne fonctionne que si vos numéros sont uniques et non classés.
Je pense qu'il y a aussi une
HashSet<T>
classe dans .NET 3.5, elle n'autorise également que des éléments uniques.la source
Une SortedList sera plus rapide à rechercher (mais plus lente à insérer des éléments)
la source
Ce n'est pas exactement une réponse à votre question, mais j'ai une classe qui augmente les performances de Contains () sur une collection. J'ai sous-classé une file d'attente et ajouté un dictionnaire qui mappe les codes de hachage aux listes d'objets. La
Dictionary.Contains()
fonction est O (1) tandis queList.Contains()
,Queue.Contains()
etStack.Contains()
sont O (n).Le type de valeur du dictionnaire est une file d'attente contenant des objets avec le même hashcode. L'appelant peut fournir un objet de classe personnalisé qui implémente IEqualityComparer. Vous pouvez utiliser ce modèle pour les piles ou les listes. Le code n'aurait besoin que de quelques modifications.
Mes tests simples montrent que mes
HashQueue.Contains()
courses sont beaucoup plus rapides queQueue.Contains()
. L'exécution du code de test avec un nombre défini sur 10 000 prend 0,00045 secondes pour la version HashQueue et 0,37 seconde pour la version Queue. Avec un nombre de 100 000, la version HashQueue prend 0,0031 seconde alors que la file d'attente prend 36,38 secondes!Voici mon code de test:
la source
HashQueue, 00:00:00.0004029
Queue, 00:00:00.3901439
HashSet, 00:00:00.0001716
Pourquoi un dictionnaire est-il inapproprié?
Pour voir si une valeur particulière figure dans la liste, vous devez parcourir toute la liste. Avec un dictionnaire (ou un autre conteneur basé sur le hachage), il est beaucoup plus rapide de réduire le nombre d'objets à comparer. La clé (dans votre cas, le nombre) est hachée et cela donne au dictionnaire le sous-ensemble fractionnaire d'objets à comparer.
la source
J'utilise ceci dans le cadre compact où il n'y a pas de support pour HashSet, j'ai opté pour un dictionnaire où les deux chaînes sont la valeur que je recherche.
Cela signifie que j'obtiens la fonctionnalité de liste <> avec les performances du dictionnaire. C'est un peu hacky, mais ça marche.
la source
string
référence et unebool
valeur font une différence de 3 ou 7 octets, respectivement pour les systèmes 32 ou 64 bits. Notez, cependant, que la taille de chaque entrée est arrondie aux multiples de 4 ou 8, respectivement. Le choix entrestring
etbool
pourrait donc ne faire aucune différence dans la taille. La chaîne vide""
existe toujours en mémoire déjà en tant que propriété statiquestring.Empty
, donc cela ne fait aucune différence que vous l'utilisiez dans le dictionnaire ou non. (Et il est utilisé ailleurs de toute façon.)