Le coût de passage par shared_ptr
J'utilise std::tr1::shared_ptr largement tout au long de ma demande. Cela inclut la transmission des objets comme arguments de la fonction. Considérez les points suivants:
class Dataset {...}
void f( shared_ptr< Dataset const > pds ) {...}
void g( shared_ptr< Dataset const > pds ) {...}
...
Tout en passant un objet dataset autour de la via shared_ptr garantit son existence à l'intérieur de f et g, les fonctions peuvent être appelées des millions de fois, ce qui provoque beaucoup de shared_ptr objets créés et détruits. Voici un extrait de l'appartement gprof profil à partir d'une récente:
Chaque échantillon compte que 0,01 secondes. % cumulatif total de soi secondes) (secondes les appels s/demande/appel nom 9.74 295.39 35.12 2451177304 0.00 0.00 std::tr1::__shared_count::__shared_count(std::tr1::__shared_comte const&) 8.03 324.34 28.95 2451252116 0.00 0.00 std::tr1::__shared_count::~__shared_count()
Donc, ~17% de l'exécution a été dépensé sur le comptage de référence avec shared_ptr objets. Est-ce normal?
Une grande partie de mon application est mono-thread, et je pensais à ré-écrire certaines des fonctions que
void f( const Dataset& ds ) {...}
et en remplaçant les appels
shared_ptr< Dataset > pds( new Dataset(...) );
f( pds );
avec
f( *pds );
dans des endroits où je sais pour sûr, l'objet ne sera pas détruit, tandis que le flux du programme est à l'intérieur de f(). Mais avant de courir pour changer un tas de signatures de fonction /appels, je voulais savoir quelles sont les performances habituelles frappé de passer par shared_ptr a été. Semble comme shared_ptr ne doit pas être utilisé pour des fonctions qui appelés très souvent.
Toute entrée serait appréciée. Merci pour la lecture.
-Artem
Mise à jour: Après un changement de poignée de fonctions d'accepter const Dataset&
, le nouveau profil ressemble à ceci:
Chaque échantillon compte que 0,01 secondes. % cumulatif total de soi secondes) (secondes les appels s/demande/appel nom 0.15 241.62 0.37 24981902 0.00 0.00 std::tr1::__shared_count::~__shared_count() 0.12 241.91 0.30 28342376 0.00 0.00 std::tr1::__shared_count::__shared_count(std::tr1::__shared_comte const&)
Je suis un peu perplexe par le nombre d'appels de destructeur étant plus petite que le nombre de constructeur de copie d'appels, mais dans l'ensemble je suis très heureux avec la diminution de l'associé au moment de l'exécution. Merci à tous pour leurs conseils.
- Question connexe: stackoverflow.com/questions/327573/...
- Question connexe: sur certaines plates-formes (par exemple, âgés de BRAS), le comptage de référence nécessite de verrouiller un mutex. Cela peut faire partagé pointeurs inutilisable (sauf par référence) dans un réel contexte de temps.
Vous devez vous connecter pour publier un commentaire.
Toujours passer votre
shared_ptr
par const référence:Edit: Concernant les problèmes de sécurité mentionnés par d'autres:
shared_ptr
fortement l'ensemble de l'application, en passant par la valeur va prendre énormément de temps (je l'ai vu aller à+ de 50%).const T&
au lieu deconst shared_ptr<T const>&
lorsque l'argument ne doit pas être null.const shared_ptr<T const>&
est plus sûr queconst T*
lorsque la performance est un problème.pds
être non null, préféronsconst Dataset&
. Vous avez seulement besoin de passer unshared_ptr
si la valeur null est une valeur valide.Vous avez besoin shared_ptr seulement de les transmettre à des fonctions/objets qui les conserver pour une utilisation future. Par exemple, une classe peut garder shared_ptr pour l'utilisation dans un thread de travail. Pour de simples appels synchrones, il est tout à fait suffisant pour l'utilisation de la plaine de pointeur ou de référence. shared_ptr ne devrait pas remplacer l'aide de la plaine des pointeurs complètement.
Si vous ne l'utilisez pas make_shared, pourriez-vous nous donner un aller? En localisant le compteur de référence et l'objet dans la même zone de mémoire vous peut voir un gain de performance associés à de cohérence de cache. Vaut la peine d'essayer de toute façon.
Toute création et destruction d'objets, surtout redondant création et destruction d'objets, doivent être évités dans les performances des applications critiques.
Considérer ce shared_ptr est en train de faire. Non seulement est-il de la création d'un nouvel objet et de le remplir, mais c'est aussi du référencement de l'état partagé pour incrémenter les informations de référence, et l'objet lui-même, vraisemblablement, de la vie ailleurs complètement qui va être cauchemardesque sur votre cache.
Probablement vous avez besoin de la shared_ptr (parce que si vous pouviez sortir avec un objet local, vous ne serait pas de consacrer un hors du tas), mais vous pouvez même "cache" le résultat de la shared_ptr de déréférencement:
...parce que même *pds nécessite de frapper plus de mémoire que ce qui est absolument nécessaire.
Il semble que vous savez vraiment ce que vous faites. Vous avez profilé de votre application, et vous savez exactement où les cycles sont utilisés. Vous comprenez que le fait d'appeler le constructeur pour un comptage de référence de pointeur est cher que si vous le faites en permanence.
La seule heads up, je peux vous donner est: supposons que l'intérieur de la fonction f(t *ptr), si vous appelez une fonction qui utilise des pointeurs partagés, et de vous en faire d'autres(ptr) et d'autres marques partagé un pointeur de pointeur brut. Lorsque ce deuxième partagé pointeurs de référence décompte arrive à 0 alors que vous avez supprimé de votre objet....même si vous ne voulez pas. vous avez dit que vous avez utilisé le comptage de références pointeurs beaucoup, de sorte que vous avez à surveiller du coin de tels cas.
EDIT:N'empêche pas les multiples suppressions de pointeurs partagés. Comme chaque commentaire de Mat.Vous pouvez faire le destructeur privé, et seulement un ami de le pointeur partagé de la classe, de façon à ce que le destructeur ne peut être appelée que par un pointeur partagé, alors vous êtes en sécurité.
shared_ptr
pointant vers le même objet physique, une piscine pour le supprimer avant les autres...