Le moyen le plus efficace pour affecter des valeurs à des cartes
Qui pour affecter des valeurs à une carte qui est le plus efficace? Ou sont-ils tous optimisés pour le même code (sur la plupart des les compilateurs modernes)?
//1) Assignment using array index notation
Foo["Bar"] = 12345;
//2) Assignment using member function insert() and STL pair
Foo.insert(std::pair<string,int>("Bar", 12345));
//3) Assignment using member function insert() and "value_type()"
Foo.insert(map<string,int>::value_type("Bar", 12345));
//4) Assignment using member function insert() and "make_pair()"
Foo.insert(std::make_pair("Bar", 12345));
(Je sais que je pourrais indice de référence et de vérifier la sortie du compilateur, mais cette question se pose maintenant et la seule chose que j'ai à portée de main est mon téléphone portable... hehe)
Je serais prêt à parier sur
en fait,
they are all the same
. Je pourrais expliquer pourquoi l'un serait plus rapide que les autres, mais ce serait seulement si nous ignorons les optimisations du compilateur.en fait,
4)
œuvres, std::pair<std::string, int> p = std::make_pair("Bar", 12345);
parce que le constructeur de pair
est permissive de cette façon (aussi longtemps que les types peuvent être convertis, cela fonctionne).OriginalL'auteur inquam | 2013-01-08
Vous devez vous connecter pour publier un commentaire.
Tout d'abord, il y a des différences sémantiques entre
[]
etinsert
:[]
sera remplacer l'ancienne valeur (le cas échéant)insert
sera ignorer la nouvelle valeur (si une ancienne valeur est déjà présente)donc de comparer les deux est inutile en général.
Concernant les inserts versions:
std::map<std::string, int>::value_type
eststd::pair<std::string const, int>
donc pas de (important) différence entre 3 et 4std::make_pair("Bar", 12345)
est moins cher questd::pair<std::string, int>("Bar", 12345)
parce que lestd::string
type est un à part entière de la classe, avec des opérations à faire sur la copie et"Bar"
est juste une chaîne littérale (donc un pointeur copie); cependant, car à la fin vous avez besoin de créer lastd::string
... ça dépendra de la qualité de votre compilateurEn général, je recommande:
[]
pour la mise à jour deinsert(std::make_pair())
pour ignorer les doublonsstd::make_pair
n'est pas seulement plus court, il respecte également le SEC directive: Ne pas se Répéter.Pour l'exhaustivité, de la manière la plus rapide (et plus simple) serait
emplace
(C++11 activé):Son comportement est celui de
insert
, mais il construit le nouvel élément en place.[]
va remplacer la valeur existante. Le même est vrai pour cette notation sur la plupart des structures de données. Je pensais donc à une propre carte et juste en l'insérant une valeur unique. Mais puisque les autres le faire réellement de savoir si ou non une ancienne valeur existe, je suppose qu'il doit y avoir un code supplémentaire généré à cet effet. Ainsi, ce qui les rend différents de[]
.est sujette à être un peu plus lent, car il s'agit de construire une valeur vide et immédiatement jeter... cependant, c'est seulement un problème si la construction de la valeur vide est lent dans la première place (évidemment).
Une implémentation classique de
insert
: 1) l'utilisation interne des fonctions de recherche pour obtenir le nœud de l'arborescence, le cas échéant 2) si elle existe, de retour que d'autre 3) utiliser le résultat de la trouver pour insérer la nouvelle valeur et retourner. Une mise en œuvre pouroperator[]
permettra de faire exactement la même chose, sauf 3) insère un défaut de la valeur construite, et des rendements. (Vous pouvez ensuite remplacer ce qui a été retourné.)std::map<std::string, int>::value_type
eststd::pair<const std::string, int>
, passtd::pair<std::string, int>
. Par conséquent, seuls 3) a un type qui peut être inséré directement sans conversion de type (ou la surcharge de[]
). Cette surcharge peut être supprimée par le compilateur, mais en général, je ne voudrais pas compter sur elle.si nous partons de cette route,
emplace
est encore mieux -> au-lieu de construction seulement lorsque nécessaire.OriginalL'auteur Matthieu M.
1) peut être un peu plus lent que les autres méthodes car
std::map::operator[]
premier défaut-crée l'objet, si elle n'existe pas déjà, puis renvoie une référence que vous pouvez utiliseroperator=
sur pour définir la valeur souhaitée, c'est à dire deux opérations.2-4) doit être équivalent depuis
map::value_type
est un typedef pourstd::pair
pour les mêmes types, et doncmake_pair
est également équivalent. Le compilateur doit traiter de la même manière.Également noter que le rendement peut être encore plus élevé si vous avez besoin à la fois de vérifier l'existence (par exemple pour effectuer une logique spéciale selon qu'il existe ou pas) et puis aussi l'insérer, en utilisant
map::lower_bound
d'abord obtenir un indice, à l'endroit où l'élément doit être, donc,map::insert
n'a pas à rechercher l'ensemblemap
de nouveau:non;
mymap.key_comp()(key, hint->first)
vérifie sikey
est à moins dehint->first
. Sitrue
, cela signifie quekey
n'était pas présent (depuislower_bound
retourne un itérateur pour le premier élément, pas moins).false
signifierait quekey
est identique àhint->first
.C'est déroutant. lower_bound pouvez donner >=, ne pas < n'implique pas l' == ou !=, donc, n'auriez-vous pas besoin mymap.key_comp()(touche, touche->premier) && mymap.key_comp()(indice->tout d'abord, la clé) ou similaire?
Je suis d'accord c'est déroutant au premier abord. Comme vous le dites,
lower_bound
retourne un itérateur à un élément existant qui est soit égal ou plus que celui fournikey
senshint->first < key
serait toujoursfalse
; il n'y a pas besoin de vérifier cela. Au lieu de cela, juste vérifierkey < hint->first
nous permet de confirmer si le retour de l'itérateur est égal ((key < hint->first) == false
) ou plus ((key < hint->first) == true
), c'est à dire sikey
existe déjà dans la carte ou pas. Cela couvre tous les cas.En fait j'ai perdu contexte au cours des derniers jours depuis que je suis en train de travailler sur d'autres choses. J'ai rapidement vérifié, aucun dans le top 5 des liens ont utilisé comme valeur de comparer, mais comme un éventail, tandis que cplusplus.com/reference/map/map/key_comp tutorialspoint.com/cpp_standard_library/... , je ne dis pas que vous avez tort, mais je ne vois pas ce que vous dites clairement.
OriginalL'auteur Anders Johansson
Votre première possibilité:
Foo["Bar"] = 12345;
a quelque peu différente de la sémantique que les autres, il va insérer un nouvel objet si il n'en existe pas (comme les autres) mais remplacer le contenu actuel si aucun n'existe, (où les autres à l'aide deinsert
échouera si cette clé est déjà présente).Autant que la vitesse augmente, il a le potentiel d'être plus lent que les autres. Lorsque vous êtes à l'insertion d'un nouvel objet, il a créer une paire avec la clé spécifiée et un défaut construit value_type, puis affecter le bon value_type par la suite. Les autres construire la paire la bonne clé et de la valeur et de l'insertion de l'objet. Être juste, cependant, mon expérience est que la différence est rarement significative (avec les anciens compilateurs, il a été plus importante, mais avec les plus récents assez minime).
Les deux sont équivalents les uns aux autres. Vous êtes tout simplement à l'aide de deux différentes façons de nommer le même type. Par moment de l'exécution, il n'y a pas de différence entre eux.
La quatrième utilise une fonction de modèle (make_pair) qui, théoriquement, pourrait impliquent un niveau supplémentaire d'appel de fonction. Je serais très surpris de voir une réelle différence de ce que, à l'exception (peut-être) si vous avez été prudent afin de s'assurer que le compilateur n'a absolument pas optimisation (surtout inlining).
Ligne du bas: Le premier sera souvent un peu plus lent que le reste (mais pas toujours et pas de beaucoup). Les trois autres seront presque toujours être égal (comme dans: normalement s'attendre raisonnablement compilateur pour produire un code identique pour tous les trois) même si il y a justification théorique pour le quatrième étant plus lent.
OriginalL'auteur Jerry Coffin
Si il n'y a pas d'objet à l'emplacement de la clé, puis:
std::map::emplace
est le plus efficace.insert
est deuxième (mais sera très proche).[]
est moins efficace.[]
, si il n'y a pas d'objet, trivial constructions. Il appelle ensuiteoperator=
.insert
un constructeur de copie d'appel sur lestd::pair
argument.Toutefois, dans le cas de cartes,
map.insert( make_pair( std::move(key), std::move(value) ) )
va être proche demap.emplace( std::move(key), std::move(value) )
.Si il y a un objet à l'emplacement de la clé, puis
[]
appelleraoperator=
, tandis queinsert
/emplace
va détruire l'ancien objet et en créer un nouveau.[]
pourrait facilement être moins cher dans ce cas.En fin de compte, cela dépend de ce que votre
operator=
vs copier-construire vs trivial-construire vs destructeur frais sont à votre clé et la valeur.Le travail réel à la recherche des choses à l'intérieur de la structure de l'arbre de la
std::map
sera si près identiques, il n'est pas drôle.OriginalL'auteur Yakk - Adam Nevraumont
Le troisième est le meilleur choix (à mon humble avis), mais 2, 3 et 4 sont égaux.
Pourquoi je pense que le troisième est le meilleur choix: Vous effectuez un seul opération pour insérer la valeur: il suffit d'insérer (bon, il y a une recherche trop) et vous pouvez savoir si la valeur a été inséré, la vérification de la
second
membre de la valeur de retour et la mise en œuvre des subventions pour ne pas écraser la valeur.L'utilisation de la
value_type
a des avantages: vous n'avez pas besoin de connaître le mappé type ou un type de clé est donc utile avec le modèle de programmation.Le pire (à mon humble avis) est le premier:
Vous appelez le
std::map::operator[]
qui crée un objet et renvoie une référence à elle, alors l'objet associéoperator =
est appelé. Vous êtes en train de faire deux opérations en faveur de l'insertion: d'abord, l'insertion, le deuxième le asignation.Et il ont un autre problème: vous ne savez pas si la valeur a été inséré ou écrasé.
3)
plus vite que2)
?Elles sont équivalentes (comme dit dans d'autres réponses) je n'ai parlé que des que l'on est le meilleur... donc je suppose que j'ai manqué à ce point de ma réponse.
2,3,4 pourrait être égaux, a fourni un bon optimiseur. Toutefois, dans le cas d'optimisation non 3) devrait être le plus rapide, depuis le 2) et 4) engager un autre type de conversion à
std::pair<const std::string, int>
.Bon point @Grizzly!
OriginalL'auteur PaperBirdMaster
Même si il y a eu une couple de bonnes réponses déjà j'ai pensé que je pourrais aussi bien faire un rapide test. Couru chacun de 5 millions de fois et utilisé le c++11 est le chronomètre pour mesurer le temps qu'il a fallu.
Voici le code:
La sortie pour 5 millions d'itérations est:
Version de GCC:
Ma machine:
EDIT1: Fixe le code et refait le test.
EDIT2: Ajout de Matthieu M. suggestion pour le C++11 est emplace et il est droit, emplace est plus rapide
insert
versions ici ne sera jamais fait insérer, alors que la[]
version toujours attribuer. Donc c'est foutu pour commencer...M. Modifié un peu le code à insérer au point différent à chaque fois, est-ce mieux?
Je pense que le code a encore deux problèmes: 1) Vous devriez probablement désactiver la carte entre les boucles, car la façon dont il est maintenant seulement la première boucle se fait
insert
rien 2) la création d'unstringstream
est assez cher, donc cela pourrait influencer les mesures.std::to_string
peut-être moins d'impact option (je ne suis pas entièrement sûr si cela a un impact compte tenu de la finale de la mapsize, mais mieux vaut être sûr).Modifié afin de tenir compte de vos suggestions et refait le test.
Je ne suis pas sûr que j'ai confiance en cette méthode d'essai. Je reçois que 2) est plus rapide avec 9946 ms et 5) plus longs, avec 14557 mme. 1,3,4) sont tous à environ 10000 ms, de même que 2).
OriginalL'auteur Edward A