2010-06-04 5 views
3

Quelle doit être la taille d'une collection pour que std :: map devance un std :: vector> trié?Quelle doit être la taille d'une collection pour std :: map <k,v> pour surpasser un std :: vector <std :: pair <k,v>>?

J'ai un système où j'ai besoin de plusieurs milliers de conteneurs associatifs, et std::map semble avoir beaucoup de surcharge en termes de cache CPU. J'ai entendu quelque part que pour les petites collections std :: vector peut être plus rapide - mais je me demande où cette ligne est ....

EDIT: Je parle de 5 articles ou moins à la fois dans une structure donnée. Je suis le plus concerné par le temps d'exécution, pas par l'espace de stockage. Je sais que des questions comme celles-ci sont intrinsèquement spécifiques à une plate-forme, mais je cherche une «règle empirique» à utiliser.

Billy3

+3

Cette question est trop vague est. Sur quelle plateforme? Pour quelle charge de travail? Quelle sera la taille des conteneurs? Que sont k et v? Comment allez-vous modifier et accéder aux collections? Allez-vous souvent accéder à des valeurs à partir de clés arbitraires (aléatoires)? –

+0

Je ne suis pas sûr de comprendre la question? Que signifie «dépasser»? Pour avoir plus de mémoire? Recherche plus rapide? Pouvez-vous reformuler la question? Merci. – utnapistim

+0

@utnapistim: J'ai modifié la question. Est-ce que j'ai plus de sens? –

Répondre

8

Ce n'est pas vraiment une question de taille, mais d'utilisation. Un vecteur trié fonctionne bien lorsque le modèle d'utilisation est que vous lisez les données, puis vous effectuez des recherches dans les données.

Une carte fonctionne bien lorsque le modèle d'utilisation implique un mélange plus ou moins arbitraire de modification des données (ajout ou suppression d'éléments) et de requêtes sur les données. La raison à cela est assez simple: une carte a un surcoût plus élevé sur une recherche individuelle (grâce à l'utilisation de nœuds liés au lieu d'un bloc de stockage monolithique). Une insertion ou suppression qui maintient l'ordre, cependant, a une complexité de seulement O (lg N). Une insertion ou une suppression qui maintient l'ordre dans un vecteur a une complexité de O (N) à la place.

Il existe, bien sûr, diverses structures hybrides qui peuvent également être utiles. Par exemple, même lorsque les données sont mises à jour de manière dynamique, vous commencez souvent avec un grand nombre de données et vous effectuez un nombre relativement réduit de modifications à la fois. Dans ce cas, vous pouvez charger vos données en mémoire dans un vecteur trié et conserver le (petit nombre) d'objets ajoutés dans un vecteur séparé. Puisque ce second vecteur est normalement assez petit, vous ne vous souciez pas de le trier. Quand/si cela devient trop grand, vous le trier et le fusionner avec l'ensemble de données principal.

Édition2: (en réponse à la modification en question). Si vous parlez de 5 articles ou moins, vous êtes probablement mieux ignorer tous les ci-dessus. Laissez simplement les données non triées et effectuez une recherche linéaire. Pour une collection aussi petite, il n'y a pratiquement aucune différence entre une recherche linéaire et une recherche binaire. Pour une recherche linéaire, vous prévoyez de numériser la moitié des éléments en moyenne, ce qui donne ~ 2,5 comparaisons. Pour une recherche binaire, vous parlez du journal N, qui (si mes maths travaillent cette heure du matin) correspond à ~ 2.3 - trop petite différence à prendre en compte ou à remarquer (en fait, une recherche binaire a suffisamment de temps qu'il pourrait facilement finir plus lentement).

+0

Ceci. Si votre conteneur n'a que cinq éléments en t, tout ce que vous voulez ne fera aucune différence. – Puppy

1

Si vous dites « outspace » tu veux dire consommer plus d'espace (aka la mémoire), il est très probable que le vecteur sera toujours plus efficace (la mise en œuvre sous-jacente est un tableau de mémoire continue sans données d'Othe, où map est un arbre, donc chaque donnée implique d'utiliser plus d'espace). Cela dépend cependant de combien le vecteur réserve un espace supplémentaire pour les insertions futures.

Quand il s'agit de temps (et non d'espace), le vecteur sera aussi toujours plus efficace (faire une recherche dichotomique). Mais il sera extreamly mauvais pour ajouter de nouveaux éléments (ou les enlever).

Donc: pas de réponse simple! Recherchez les complexités, réfléchissez aux utilisations que vous allez faire. http://www.cplusplus.com/reference/stl/

+0

+1 pour le vecteur std :: vous devez trier chaque insertion. – Nikko

+0

@Nikko: Vous pouvez tout insérer au bon endroit sans aucun tri ('lower_bound + insert'), mais oui, sauf s'il y aura des insertions et des suppressions, un vecteur trié est toujours préférable à une carte. – UncleBens

+0

Peut-être est-il préférable d'utiliser une liste std :: list? – Nikko

0

EDIT: Voyant que vous parlez de 5 articles ou moins:

Le tri implique des articles échange. Lors de l'insertion dans std :: map, cela n'impliquera que des échanges de pointeurs. La vitesse d'échange d'un vecteur ou d'une carte dépend de la rapidité d'échange de deux éléments.


Je vous suggère de profiler votre application pour le comprendre.


Si vous voulez une règle simple et générale, alors vous êtes hors de la chance - vous devez considérer au moins les facteurs suivants:

temps

  • Comment insérez-vous souvent de nouveaux éléments par rapport à la fréquence de recherche?
  • Pouvez-vous insérer des lots de nouveaux articles?
  • Combien coûte le tri? Les vecteurs d'éléments qui sont coûteux à échanger deviennent très coûteux à trier - les vecteurs de pointeurs prennent beaucoup moins.

mémoire

  • Combien les frais généraux par répartition ne l'allocateur que vous utilisez avoir? std :: map effectuera une allocation par item.
  • Quelle est la taille de vos paires clé/valeur?
  • Quelle est la taille de vos pointeurs? (32/64 bits)
  • Quelle est la rapidité de l'implémentation de std :: vector? (Facteurs de croissance populaires sont 1,5 et 2)

Passé une certaine taille du récipient et élément, les frais généraux d'allocation et pointeurs arbres deviendrai contrebalancés par le coût de la mémoire inutilisée à la fin du vecteur - mais de loin la façon la plus facile de savoir si et quand cela se produit est de mesurer.

0

Il doit être dans le millionième élément. Et même là ...

Je pense plus ici à l'utilisation de la mémoire et aux accès mémoire. Sous des centaines de milliers, prenez ce que vous voulez, il n'y aura pas de différence notable. Les processeurs sont très rapides ces jours-ci, et le goulot d'étranglement est la latence de la mémoire.

Mais même avec des millions d'objets, si votre carte <> a été créée en insérant des éléments dans un ordre aléatoire. Lorsque vous souhaitez parcourir votre carte (dans l'ordre trié), vous finirez par sauter de manière aléatoire dans la mémoire, bloquant le processeur pour que la mémoire soit disponible, ce qui entraîne des performances médiocres. D'un autre côté, si vos millions d'éléments sont dans un vecteur, le traverser est très rapide, en profitant des prédictions de la mémoire de la CPU.

Comme d'autres l'ont écrit, cela dépend de votre utilisation.

Editer: Je m'interrogerais davantage sur la façon d'organiser vos milliers de conteneurs associatifs que les conteneurs eux-mêmes s'ils ne contiennent que 5 éléments.

+0

Chaque conteneur est associé à un fichier. Il contient des blobs de données qui sont associés à ce fichier, comme le hachage MD5. La recherche de carte enregistre à nouveau le calcul du MD5 pour ce fichier. Cette application parcourt l'arborescence pour rechercher des éléments "intéressants" sur le système de fichiers. Ainsi, chaque fichier n'a besoin que de 5 éléments à la fois, un pour les attributs, un pour MD5, un pour .... etc. Mais puisqu'il y a des milliers de fichiers, la performance du conteneur devient significative. –

+0

C'est exactement ce que je veux dire. Ne vous souciez pas du conteneur car il n'a que peu d'éléments de petite taille, utilisez le vecteur pour sa simplicité et son comportement prédictif (contrairement à la carte où le comportement et les performances dépendent du fait que les différents éléments sont alloués dans des emplacements mémoire contigus ou non. fait la différence!).Portez plus d'attention à votre conteneur de conteneurs. –

+0

Mon conteneur de conteneurs n'a pas d'importance car peu de fichiers sont réellement en mémoire à un moment donné - un, pour être précis. (Sauf si les choses doivent être triées, auquel cas j'utilise un deque) La raison pour le conteneur est qu'il y a ~ 35 types d'info que je voudrais associer à un fichier, mais en mettant de l'espace pour chacun d'entre eux Dans la classe associée à un fichier, la classe A. en fait trop et B. est trop grande. Je m'attends à environ 5 à utiliser à la fois mais il est tout à fait possible que ce soit plus. –

1

Le problème principal avec std::map est un problème de cache, comme vous l'avez indiqué.

Le vecteur trié est une approche bien connue: Loki::AssocVector.

Pour les très petits ensembles de données, le AssocVector doit écraser la carte malgré la copie impliquée lors de l'insertion simplement à cause de la localisation du cache. Le AssocVector surpasse également la carte pour une utilisation en lecture seule. La recherche binaire y est plus efficace (moins de pointeurs à suivre).

Pour toutes les autres utilisations, vous aurez besoin de profil ...

Il y a cependant une alternative hybride que vous pourriez envisager: en utilisant le paramètre Allocator de la carte pour limiter la zone de mémoire où les éléments sont alloués, minimisant ainsi le problème de référence de localité (la racine des échecs de cache).

Il y a aussi un changement de paradigme que vous pourriez envisager: avez-vous besoin d'éléments triés ou d'une recherche rapide?

En C++, les seuls conteneurs compatibles STL pour la recherche rapide ont été implémentés en termes de conteneurs associatifs triés depuis des années. Cependant le prochain C++ 0x comporte le unordered_map tant attendu qui pourrait surpasser toutes les solutions ci-dessus!

+0

La mappe non ordonnée n'est pas utilisable dans mon implémentation car A. elle nécessite un algorithme de hachage et B. sa surcharge de mémoire est trop élevée. En ce qui concerne l'allocateur, la plupart des implémentations courantes de la liste STL le font déjà pour les conteneurs associatifs standard. Je doute que tout ce que vous ou moi écririons soit plus performant que l'implémentation de Dinkumware ou de SGI. –

+0

@Billy: L'allocateur de la carte std :: map pour VS2005 (Dinkumware) utilisera tout simplement new-> malloc pour chaque noeud de la carte. Comment cela est-il bon pour les problèmes de référence de la localité? –

+0

@Martin: Si vous utilisez 2 versions de VC++, alors allez-y et écrivez votre propre allocateur;) (Bien que je ne sache pas pourquoi vous commentez une question vieille de 6 mois ...) –

Questions connexes