web-dev-qa-db-fra.com

performances de la carte stl?

J'utilise map<MyStruct, I*> map1;. Apparemment, 9% du temps total de mon application y est consacré. Plus précisément sur une ligne de l'une de mes fonctions principales. La carte n'est pas très grande (<1k presque toujours, <20 est commun).

Existe-t-il une autre implémentation que je souhaiterais utiliser? Je pense que je ne devrais pas écrire le mien mais je pourrais si je pensais que c'était une bonne idée.

Informations supplémentaires: je vérifie toujours avant d'ajouter un élément. Si une clé existe, je dois signaler un problème. Qu'après un point, j'utiliserai beaucoup la carte pour les recherches et n'ajoutera plus d'éléments.

20
user34537

Vous devez d'abord comprendre ce qu'est une carte et ce que les opérations que vous faites représentent. Un std::map Est un arbre binaire équilibré, la recherche prendra O( log N ) opérations, dont chacune est une comparaison des touches plus quelques extra que vous pouvez ignorer dans la plupart des cas (gestion des pointeurs). L'insertion prend à peu près le même temps pour localiser le point d'insertion, plus l'allocation du nouveau nœud, l'insertion réelle dans l'arborescence et le rééquilibrage. La complexité est à nouveau O( log N ) bien que les constantes cachées soient plus élevées.

Lorsque vous essayez de déterminer si une clé se trouve dans la carte avant l'insertion, vous encourez le coût de la recherche et si elle échoue, le même coût pour localiser le point d'insertion. Vous pouvez éviter le coût supplémentaire en utilisant std::map::insert Qui retourne une paire avec un itérateur et un booléen vous indiquant si l'insertion s'est réellement produite ou si l'élément était déjà là.

Au-delà de cela, vous devez comprendre combien il est coûteux de comparer vos clés, ce qui ne correspond pas à ce que la question montre (MyStruct pourrait contenir un seul int ou un millier d'entre elles), ce qui est quelque chose que vous devez prendre en compte.

Enfin, il se peut qu'un map ne soit pas la structure de données la plus efficace pour vos besoins, et vous voudrez peut-être envisager d'utiliser soit un std::unordered_map (Table de hachage) qui attend un temps constant insertions (si la fonction de hachage n'est pas horrible) ou pour de petits ensembles de données, même un tableau ordonné simple (ou std::vector) sur lequel vous pouvez utiliser la recherche binaire pour localiser les éléments (cela réduire le nombre d'allocations, au prix d'insertions plus chères, mais si les types détenus sont suffisamment petits, cela pourrait valoir la peine)

Comme toujours avec la performance, mesurez puis essayez de comprendre où le temps est passé. Notez également que 10% du temps passé dans une fonction ou une structure de données particulière peut représenter beaucoup ou presque rien, selon votre application. Par exemple, si votre application effectue simplement des recherches et des insertions dans un ensemble de données, et que cela ne prend que 10% du CPU, vous avez beaucoup à optimiser partout ailleurs!

Il sera probablement plus rapide de simplement faire un insert et de vérifier si le pair.second est false si la clé existe déjà:

comme ça

if ( myMap.insert( make_pair( MyStruct, I* ) ).second == false)
{
  // report error
}
else
  // inserted new value

... plutôt que de faire un appel à find à chaque fois.

9
EdChum

Au lieu de map, vous pouvez essayer unordered_map qui utilise des clés de hachage, au lieu d'un arbre, pour trouver des éléments. Cette réponse donne quelques astuces quand préférer unordered_map sur map.

7
Christian Ammer

Cela peut être long, mais pour les petites collections, le facteur le plus critique est parfois la performance cache .

Puisque std::map implémente un arbre rouge-noir, qui [AFAIK] n'est pas très efficace en cache - peut-être implémentant la carte comme std::vector<pair<MyStruct,I*>> serait une bonne idée, et utilisez là une recherche binaire [au lieu des recherches de carte], à tout le moins, cela devrait être efficace une fois que vous commencez seulement à chercher [arrêtez d'insérer des éléments], puisque le std::vector est plus susceptible de tenir dans le cache que le map.

Ce facteur [cpu-cache] est généralement négligé et caché comme constant dans la grande notation O, mais pour les grandes collections, il peut avoir un effet majeur.

4
amit

La façon dont vous utilisez la carte, vous effectuez des recherches sur la base d'une instance MyStruct et selon votre implémentation particulière, la comparaison requise peut être coûteuse ou non.

2
Johann Gerell

Existe-t-il une autre implémentation que je souhaiterais utiliser? Je pense que je ne devrais pas écrire le mien mais je pourrais si je pensais que c'était une bonne idée.

Si vous comprenez assez bien le problème, vous devez préciser en quoi votre implémentation sera supérieure.

map est-il la bonne structure? Si c'est le cas, l'implémentation de votre bibliothèque standard sera probablement de bonne qualité (bien optimisée).

La comparaison MyStruct peut-elle être simplifiée?

Où est le problème - le redimensionnement? Chercher?

Avez-vous minimisé la copie et affecté les coûts de vos structures?

1
justin

Comme indiqué dans les commentaires, sans code approprié, il y a peu de réponses universelles à vous donner. Cependant, si MyStruct est vraiment énorme, la copie de la pile peut être coûteuse. Il est peut-être judicieux de stocker des pointeurs dans MyStruct et d'implémenter votre propre mécanisme de comparaison:

template <typename T> struct deref_cmp {
  bool operator()(std::shared_ptr<T> lhs, std::shared_ptr<T> rhs) const {
    return *lhs < *rhs;
  }
};

std::map<std::shared_ptr<MyStruct>, I*, deref_cmp<MyStruct>> mymap;

Cependant, c'est quelque chose que vous devrez profiler. Il pourrait accélérer les choses.

Vous recherchez un élément comme celui-ci

template <typename T> struct NullDeleter {
  void operator()(T const*) const {}
};
// needle being a MyStruct
mymap.find(std::shared_ptr<MyStruct>(&needle,NullDeleter()));

Il va sans dire qu'il y a plus de potentiel à optimiser.

1
bitmask