Quelle est la surcharge des pointeurs intelligents par rapport aux pointeurs normaux en C ++ 11? En d'autres termes, mon code sera-t-il plus lent si j'utilise des pointeurs intelligents, et si oui, combien plus lent?
Plus précisément, je pose des questions sur le C ++ 11 std::shared_ptr
et std::unique_ptr
.
De toute évidence, les éléments poussés vers le bas de la pile seront plus grands (du moins je le pense), car un pointeur intelligent doit également stocker son état interne (nombre de références, etc.), la question est vraiment de savoir combien cela va-t-il affecter mes performances, voire pas du tout?
Par exemple, je renvoie un pointeur intelligent à partir d'une fonction au lieu d'un pointeur normal:
std::shared_ptr<const Value> getValue();
// versus
const Value *getValue();
Ou, par exemple, lorsqu'une de mes fonctions accepte un pointeur intelligent comme paramètre au lieu d'un pointeur normal:
void setValue(std::shared_ptr<const Value> val);
// versus
void setValue(const Value *val);
la source
std::unique_ptr
oustd::shared_ptr
?Réponses:
std::unique_ptr
a une surcharge de mémoire uniquement si vous lui fournissez un suppresseur non trivial.std::shared_ptr
a toujours une surcharge de mémoire pour le compteur de référence, même si elle est très petite.std::unique_ptr
a une surcharge de temps uniquement pendant le constructeur (s'il doit copier le suppresseur fourni et / ou initialiser le pointeur par null) et pendant le destructeur (pour détruire l'objet possédé).std::shared_ptr
a une surcharge de temps dans le constructeur (pour créer le compteur de référence), dans le destructeur (pour décrémenter le compteur de référence et éventuellement détruire l'objet) et dans l'opérateur d'affectation (pour incrémenter le compteur de référence). En raison des garanties de sécurité des threadsstd::shared_ptr
, ces incréments / décréments sont atomiques, ajoutant ainsi un peu plus de temps système.Notez qu'aucun d'entre eux n'a de surcharge de temps pour le déréférencement (pour obtenir la référence à l'objet possédé), alors que cette opération semble être la plus courante pour les pointeurs.
Pour résumer, il y a une surcharge, mais cela ne devrait pas ralentir le code, sauf si vous créez et détruisez continuellement des pointeurs intelligents.
la source
unique_ptr
n'a pas de frais généraux dans le destructeur. Il fait exactement la même chose que vous le feriez avec un pointeur brut.std::unique_ptr
? Si vous construisez astd::unique_ptr<int>
, l'interneint*
est initialisé selonnullptr
que vous le vouliez ou non.Comme pour toutes les performances de code, le seul moyen vraiment fiable d'obtenir des informations concrètes est de mesurer et / ou d' inspecter le code machine.
Cela dit, un raisonnement simple dit que
Vous pouvez vous attendre à une surcharge dans les versions de débogage, puisque par exemple
operator->
doit être exécuté comme un appel de fonction afin que vous puissiez y entrer (ceci est à son tour dû au manque général de support pour marquer les classes et les fonctions comme non-débogage).Car
shared_ptr
vous pouvez vous attendre à une surcharge lors de la création initiale, car cela implique l'allocation dynamique d'un bloc de contrôle, et l'allocation dynamique est beaucoup plus lente que toute autre opération de base en C ++ (à utilisermake_shared
lorsque cela est pratiquement possible, pour minimiser cette surcharge).De plus,
shared_ptr
il y a une surcharge minime pour maintenir un décompte de références, par exemple lors du passage d'uneshared_ptr
valeur par, mais il n'y a pas une telle surcharge pourunique_ptr
.En gardant à l'esprit le premier point ci-dessus, lorsque vous mesurez, faites cela à la fois pour les versions de débogage et de publication.
Le comité international de normalisation C ++ a publié un rapport technique sur les performances , mais c'était en 2006, avant
unique_ptr
etshared_ptr
ont été ajoutés à la bibliothèque standard. Pourtant, les pointeurs intelligents étaient vieux chapeau à ce moment-là, donc le rapport en a également tenu compte. Citant la partie pertinente:En tant que supposition éclairée, le «bien dans l'état de l'art» a été atteint avec les compilateurs les plus populaires aujourd'hui, au début de 2014.
la source
Ma réponse est différente des autres et je me demande vraiment s'ils ont déjà profilé du code.
shared_ptr a une surcharge importante pour la création en raison de son allocation de mémoire pour le bloc de contrôle (qui conserve le compteur de références et une liste de pointeurs vers toutes les références faibles). Il a également une surcharge de mémoire énorme à cause de cela et du fait que std :: shared_ptr est toujours un tuple à 2 pointeurs (un vers l'objet, un vers le bloc de contrôle).
Si vous passez un shared_pointer à une fonction en tant que paramètre de valeur, il sera au moins 10 fois plus lent qu'un appel normal et créera beaucoup de codes dans le segment de code pour le déroulement de la pile. Si vous le passez par référence, vous obtenez une indirection supplémentaire qui peut également être bien pire en termes de performances.
C'est pourquoi vous ne devriez pas faire cela à moins que la fonction ne soit vraiment impliquée dans la gestion de la propriété. Sinon, utilisez "shared_ptr.get ()". Il n'est pas conçu pour garantir que votre objet n'est pas tué lors d'un appel de fonction normal.
Si vous devenez fou et utilisez shared_ptr sur de petits objets comme une arborescence de syntaxe abstraite dans un compilateur ou sur de petits nœuds dans toute autre structure de graphe, vous verrez une énorme baisse de performances et une énorme augmentation de la mémoire. J'ai vu un système d'analyse syntaxique qui a été réécrit peu de temps après l'arrivée de C ++ 14 sur le marché et avant que le programmeur n'apprenne à utiliser correctement les pointeurs intelligents. La réécriture était une magnitude plus lente que l'ancien code.
Ce n'est pas une solution miracle et les pointeurs bruts ne sont pas non plus mauvais par définition. Les mauvais programmeurs sont mauvais et la mauvaise conception est mauvaise. Concevez avec soin, concevez avec une propriété claire à l'esprit et essayez d'utiliser le shared_ptr principalement sur la limite de l'API du sous-système.
Si vous voulez en savoir plus , vous pouvez regarder M. Nicolai Josuttis bien parler de « Le prix réel de Pointeurs partagé en C ++ » https://vimeo.com/131189627
Il va profondément dans l'architecture des détails de mise en œuvre et de la CPU pour les barrières d'écriture, atomique serrures, etc. une fois que vous écoutez, vous ne parlerez jamais de cette fonctionnalité étant bon marché. Si vous voulez juste une preuve de la magnitude plus lente, ignorez les 48 premières minutes et regardez-le exécuter un exemple de code qui s'exécute jusqu'à 180 fois plus lentement (compilé avec -O3) lorsque vous utilisez un pointeur partagé partout.
la source
std::make_shared()
? De plus, je trouve que les démonstrations d'abus flagrant sont un peu ennuyeuses ...En d'autres termes, mon code sera-t-il plus lent si j'utilise des pointeurs intelligents, et si oui, combien plus lent?
Ralentissez? Probablement pas, à moins que vous ne créiez un énorme index à l'aide de shared_ptrs et que vous n'ayez pas assez de mémoire au point que votre ordinateur commence à se froisser, comme une vieille dame tombée au sol par une force insupportable de loin.
Ce qui ralentirait votre code, ce sont des recherches lentes, un traitement de boucle inutile, d'énormes copies de données et de nombreuses opérations d'écriture sur le disque (comme des centaines).
Les avantages d'un pointeur intelligent sont tous liés à la gestion. Mais les frais généraux sont-ils nécessaires? Cela dépend de votre implémentation. Disons que vous itérez sur un tableau de 3 phases, chaque phase a un tableau de 1024 éléments. La création d'un
Mais tu veux vraiment faire ça?smart_ptr
pour ce processus peut être exagéré, car une fois l'itération terminée, vous saurez que vous devez l'effacer. Ainsi, vous pourriez gagner de la mémoire supplémentaire en n'utilisant pas unsmart_ptr
...Une seule fuite de mémoire pourrait faire en sorte que votre produit ait un point de défaillance dans le temps (disons que votre programme fuit 4 mégaoctets par heure, il faudrait des mois pour casser un ordinateur, néanmoins, il se cassera, vous le savez car la fuite est là) .
C'est comme dire "votre logiciel est garanti 3 mois, alors appelez-moi pour le service".
Donc, en fin de compte, c'est vraiment une question de ... pouvez-vous gérer ce risque? utiliser un pointeur brut pour gérer votre indexation sur des centaines d'objets différents vaut la peine de perdre le contrôle de la mémoire.
Si la réponse est oui, utilisez un pointeur brut.
Si vous ne voulez même pas y réfléchir, une
smart_ptr
solution est une bonne solution viable et géniale.la source
smart_ptr
sont vraiment utiles pour les grandes équipesThats why you should not do this unless the function is really involved in ownership management
... excellente réponse, merci, voté pourJuste pour un aperçu et juste pour l'
[]
opérateur, il est ~ 5X plus lent que le pointeur brut, comme illustré dans le code suivant, qui a été compilé à l'aide degcc -lstdc++ -std=c++14 -O0
et a généré ce résultat:Je commence à apprendre le C ++, j'ai ceci en tête: il faut toujours savoir ce que tu fais et prendre plus de temps pour savoir ce que les autres ont fait dans ton C ++.
ÉDITER
Comme indiqué par @Mohan Kumar, j'ai fourni plus de détails. La version gcc est
7.4.0 (Ubuntu 7.4.0-1ubuntu1~14.04~ppa1)
, Le résultat ci-dessus a été obtenu lorsque le-O0
est utilisé, cependant, lorsque j'utilise le drapeau '-O2', j'ai obtenu ceci:Puis est passé à
clang version 3.9.0
,-O0
était:-O2
était:Le résultat de clang
-O2
est incroyable.la source
-O0
ou de débogage de code. La sortie sera extrêmement inefficace . Toujours utiliser au moins-O2
(ou de-O3
nos jours car certaines vectorisations ne sont pas effectuées-O2
)free
appel dans le test malloc, etdelete[]
pour new (ou make variablea
static), car lesunique_ptr
s appellentdelete[]
sous le capot, dans leurs destructeurs.