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.
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.
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
.
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.
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.
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?
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.