web-dev-qa-db-fra.com

La carte C++ la plus rapide?

Corrigez-moi, je me trompe, mais std :: map est une carte ordonnée. Ainsi, chaque fois que j'insère une valeur, la carte utilise un algorithme pour trier ses éléments en interne, ce qui prend un certain temps.

Mon application obtient des informations concernant certains éléments à intervalle constant.

Cette application conserve une carte qui est définie comme suit:

::std::map<DWORD, myItem*>

Au début, tous les éléments sont considérés comme "nouveaux" dans l'application. Un objet "Item" est alloué et ajouté à cette carte, en lui associant son identifiant et un pointeur.

Quand ce n'est pas un "nouvel" élément (juste une mise à jour de cet objet), mon application doit trouver l'objet sur la carte, en utilisant l'identifiant donné, et mettre à jour.

La plupart du temps, je reçois des mises à jour.

Ma question est:
Existe-t-il une implémentation plus rapide de la carte ou devrais-je continuer à utiliser celle-ci?
Puis-je utiliser unordered_map?

22
Poni

Suis-je mieux d'utiliser unordered_map?

Peut-être.

std:map fournit des performances cohérentes en O (log n) car il doit être implémenté sous forme d'arborescence équilibrée. Mais std:unordered_map sera implémenté comme une table de hachage qui pourrait vous donner O(1) performances (bonne fonction de hachage et distribution des clés sur des compartiments de hachage), mais cela pourrait être O(n) (tout dans un seau de hachage et dévolue à une liste). On s’attend normalement à quelque chose entre ces deux extrêmes.

Ainsi, vous pouvez avoir des performances raisonnables (O (log n)) tout le temps ou vous devez vous assurer que tout s'aligne pour obtenir de bonnes performances avec un hachage.

Comme pour toute question de ce type: vous devez mesurer avant de vous engager dans une approche. À moins que vos jeux de données soient volumineux, vous constaterez qu'il n'y a pas de différence significative.

39
Richard

Avertissement important: À moins que vous n'ayez mesuré (et votre question suggère que ce n'est pas le cas), les performances de la carte ont une influence considérable sur les performances de vos applications (un pourcentage important de temps est consacré à la recherche et à la mise à jour de la carte). plus rapidement. Tenez-vous en à std::map (ou std::unordered_map ou à toute implémentation disponible de hash_map). Une accélération de votre application de 1% ne vaudra probablement pas la peine d'être faite. Supprimez les bugs. au lieu.

En écho à la réponse de Richard: measure performance avec différentes implémentations de carte utilisant vos classes réelles et vos données réelles.

Quelques notes supplémentaires:

  • Comprendre la différence entre le coût prévu (les cartes de hachage sont généralement moins élevées), le pire des coûts (O (logn) pour l’arborescence binaire équilibrée mais beaucoup plus élevé pour la carte de hachage si l’insertion déclenche la réallocation du tableau de hachage) et le coût amorti (coût total divisé par le nombre d’opérations ou d’éléments, dépend de facteurs tels que la proportion d’éléments nouveaux et existants). Vous devez trouver ce qui est le plus contraignant dans votre cas. Par exemple, la réaffectation de cartes de hachage peut s'avérer trop lourde si vous devez respecter une limite de latence très faible.

  • Découvrez où se trouve le véritable goulot d'étranglement. Il se peut que le coût de la recherche sur la carte soit insignifiant comparé à, par exemple, IO coût.

  • Essayez une implémentation plus spécialisée de la carte. Par exemple, vous pouvez gagner beaucoup si vous en savez plus sur la clé de la carte. Les auteurs d'implémentations de cartes génériques n'ont pas cette connaissance.

Dans votre exemple (clés de nombre entier non signé 32 bits qui se regroupent fortement, par exemple, sont attribuées de manière séquentielle), vous pouvez utiliser une approche basée sur la base. Très exemple simple (menace comme illustration, recette pas prête à être utilisée):

Item *sentinel[65536];  // sentinel page, initialized to NULLs.
Item (*pages[65536])[65536];  // list of pages,
                              // initialized so every element points to sentinel

Ensuite, la recherche est aussi simple que:

Item *value = pages[index >> 16][index & 0xFFFF];

Lorsque vous devez définir une nouvelle valeur:

if (pages[index >> 16] == sentinel) {
  pages[index >> 16] = allocate_new_null_filled_page();
}
pages[index >> 16][index & 0xFFFF] = value;
  • Tweak votre implémentation de la carte.

    • Par exemple. chaque hash_map aime connaître le nombre approximatif d'éléments à l'avance. Cela permet d'éviter une réallocation inutile de la table de hachage et (éventuellement) un rehachage de toutes les clés. 

    • Avec mon exemple spécialisé ci-dessus, vous voudriez certainement essayer différentes tailles de page, ou une version à trois niveaux.

    • L'optimisation commune fournit un allocateur de mémoire spécialisé afin d'éviter plusieurs allocations de petits objets.

9
Tomek Szpakowicz

Chaque fois que vous insérez ou supprimez un élément, l'allocation de mémoire/la désallocation coûte cher. Au lieu de cela, vous pouvez utiliser un allocateur comme celui-ci: https://github.com/moya-lang/Allocator qui accélère std :: map deux fois plus que l'auteur dit, mais je l'ai trouvé encore plus rapidement, en particulier pour les autres conteneurs STL .

0
no one special