web-dev-qa-db-fra.com

shared_ptr: vitesse horrible

En comparant deux variantes de pointeurs - classique contre shared_ptr - j'ai été surpris par une augmentation significative de la vitesse d'exécution du programme. Pour tester l'algorithme d'insertion incrémentielle 2D Delaunay a été utilisé.

Paramètres du compilateur:

VS 2010 (version)/O2/MD/GL, W7 Prof, CPU 3.GHZ DualCore

Résultats:

shared_ptr (C++ 0x00):

N[points]         t[sec]  
100 000                6  
200 000               11  
300 000               16  
900 000               36  

Pointeurs:

N[points]         t[sec]  
100 000              0,5  
200 000               1  
300 000               2  
900 000               4   

Le temps d'exécution des versions shared_ptr est environ 10 fois plus long. Est-ce dû aux paramètres du compilateur ou à la mise en œuvre de C++ 0x00 shared_ptr est si lente?

VS2010 Profiler: Pour les pointeurs bruts, environ 60% du temps est consacré à la recherche heuristique du triangle contenant le point inséré (c'est OK, c'est un fait bien connu). Mais pour la version shared_ptr, environ 58% du temps est consacré à shared_ptr.reset () et seulement 10% est utilisé pour la recherche heuristique.

Test de code avec des pointeurs bruts:

void DT2D::DT ( Node2DList *nl, HalfEdgesList *half_edges_dt, bool print )
{
    // Create 2D Delaunay triangulation using incremental insertion method
    unsigned int nodes_count_before = nl->size();

    // Remove duplicit points
    nl->removeDuplicitPoints();

    // Get nodes count after deletion of duplicated points
    unsigned int nodes_count_after = nl->size();

    //Print info
    std::cout << "> Starting DT, please wait... ";
    std::cout << nodes_count_after << " points, " << ( nodes_count_before - nodes_count_after ) << " removed.";

    // Are in triangulation more than three points
    try
    {
            //There are at least 3 points
            if ( nodes_count_after > 2 )
            {
                    // Create simplex triangle
                    createSimplexTriangle ( nl, half_edges_dt );

                    // Increment nodes count
                    nodes_count_after += 3;

                    // Starting half Edge using for searching
                    HalfEdge *e_heuristic = ( *half_edges_dt ) [0];

                    // Insert all points into triangulation using incremental method
                    for ( unsigned int i = 3; i < nodes_count_after; i++ )  // Jump over simplex
                    {
                            DTInsertPoint ( ( *nl ) [i], &e_heuristic, half_edges_dt );
                    }

                    //Corect boundary triangles (swap edges in triangles adjacent to simplex triangles).
                    //They are legal due to DT, but not creating the convex hull )
                    correctBoundaryTriangles ( nl, half_edges_dt );

                    // Remove triangles having simplex points
                    removeSimplexTriangles ( nl, half_edges_dt );
            }

            //Print results
            std::cout << " Completed." << std::endl;
    }

Procédure d'insertion de point:

void DT2D::DTInsertPoint ( Point2D *p, HalfEdge **e1, HalfEdgesList *half_edges_dt )
{
    // One step of the Delaunay triangulation, incremental insertion by de Berg (2001)
    short   status = -1;

    //Pointers
    HalfEdge *e31 = NULL;
    HalfEdge *e21 = NULL;
    HalfEdge *e12 = NULL;
    HalfEdge *e32 = NULL;
    HalfEdge *e23 = NULL;
    HalfEdge *e13 = NULL;
    HalfEdge *e53 = NULL;
    HalfEdge *e44 = NULL;
    HalfEdge *e63 = NULL;

    try
    {
            // Test, if point lies inside triangle
            *e1 = LawsonOrientedWalk::findTriangleWalk ( p, &status, *e1, 0 );

            if ( e1 != NULL )
            {
                    // Edges inside triangle lies the point
                    HalfEdge *e2 = ( *e1 )->getNextEdge();
                    HalfEdge *e3 = e2->getNextEdge();

                    // Point lies inside the triangle
                    if ( status == 1 )
                    {
                            // Create first new triangle T1, twin edges set after creation
                            e31 = new HalfEdge ( p, *e1, NULL );
                            e21 = new HalfEdge ( e2->getPoint(), e31, NULL );
                            ( *e1 )->setNextEdge ( e21 );

                            // Create second new triangle T2, twin edges set after creation
                            e12 = new HalfEdge ( p, e2, NULL );
                            e32 = new HalfEdge ( e3->getPoint(), e12, NULL );
                            e2->setNextEdge ( e32 );

                            // Create third new triangle T3, twin edges set after creation
                            e23 = new HalfEdge ( p, e3, NULL );
                            e13 = new HalfEdge ( ( *e1 )->getPoint(), e23, NULL );
                            e3->setNextEdge ( e13 );

                            // Set twin edges in T1, T2, T3
                            e12->setTwinEdge ( e21 );
                            e21->setTwinEdge ( e12 );
                            e13->setTwinEdge ( e31 );
                            e31->setTwinEdge ( e13 );
                            e23->setTwinEdge ( e32 );
                            e32->setTwinEdge ( e23 );

                            // Add new edges into list
                            half_edges_dt->Push_back ( e21 );
                            half_edges_dt->Push_back ( e12 );
                            half_edges_dt->Push_back ( e31 );
                            half_edges_dt->Push_back ( e13 );
                            half_edges_dt->Push_back ( e32 );
                            half_edges_dt->Push_back ( e23 );

                            // Legalize triangle T1
                            if ( ( *e1 )->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, *e1 );
                            }

                            // Legalize triangle T2
                            if ( e2->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e2 );
                            }

                            // Legalize triangle T3
                            if ( e3->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e3 );
                            }
                    }

                    // Point lies on the Edge of the triangle
                    else if ( status == 2 )
                    {
                            // Find adjacent triangle
                            HalfEdge *e4 = ( *e1 )->getTwinEdge();
                            HalfEdge *e5 = e4->getNextEdge();
                            HalfEdge *e6 = e5->getNextEdge();

                            // Create first new triangle T1, twin edges set after creation
                            e21 = new HalfEdge ( p, e3, NULL );
                            ( *e1 )->setNextEdge ( e21 );

                            // Create second new triangle T2, OK
                            e12 = new HalfEdge ( p, e2, e4 );
                            e32 = new HalfEdge ( e3->getPoint(), e12, e21 );
                            e2->setNextEdge ( e32 );

                            // Create third new triangle T3, twin edges set after creation
                            e53 = new HalfEdge ( p, e6, NULL );
                            e4->setNextEdge ( e53 );

                            // Create fourth new triangle T4, OK
                            e44 = new HalfEdge ( p, e5, *e1 );
                            e63 = new HalfEdge ( e6->getPoint(), e44, e53 );
                            e5->setNextEdge ( e63 );

                            // Set twin edges in T1, T3
                            e21->setTwinEdge ( e32 );
                            ( *e1 )->setTwinEdge ( e44 );
                            e53->setTwinEdge ( e63 );
                            e4->setTwinEdge ( e12 );

                            // Add new edges into list
                            half_edges_dt->Push_back ( e21 );
                            half_edges_dt->Push_back ( e12 );
                            half_edges_dt->Push_back ( e32 );
                            half_edges_dt->Push_back ( e53 );
                            half_edges_dt->Push_back ( e63 );
                            half_edges_dt->Push_back ( e44 );

                            // Legalize triangle T1
                            if ( e3->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e3 );
                            }

                            // Legalize triangle T4
                            if ( e5->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e5 );
                            }

                            // Legalize triangle T3
                            if ( e6->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e6 );
                            }

                            // Legalize triangle T2
                            if ( e2->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e2 );
                            }
                    }
            }
    }
    //Throw exception
    catch ( std::bad_alloc &e )
    {
            //Free memory
            if ( e31 != NULL ) delete e31;
            if ( e21 != NULL ) delete e21;
            if ( e12 != NULL ) delete e12;
            if ( e32 != NULL ) delete e32;
            if ( e23 != NULL ) delete e23;
            if ( e13 != NULL ) delete e13;
            if ( e53 != NULL ) delete e53;
            if ( e44 != NULL ) delete e44;
            if ( e63 != NULL ) delete e63;

            //Throw exception
            throw ErrorBadAlloc ( "EErrorBadAlloc: ", "Delaunay triangulation: Can not create new triangles for inserted point p." );
    }

    //Throw exception
    catch ( ErrorMathZeroDevision &e )
    {
            //Free memory
            if ( e31 != NULL ) delete e31;
            if ( e21 != NULL ) delete e21;
            if ( e12 != NULL ) delete e12;
            if ( e32 != NULL ) delete e32;
            if ( e23 != NULL ) delete e23;
            if ( e13 != NULL ) delete e13;
            if ( e53 != NULL ) delete e53;
            if ( e44 != NULL ) delete e44;
            if ( e63 != NULL ) delete e63;

            //Throw exception
            throw ErrorBadAlloc ( "EErrorMathZeroDevision: ", "Delaunay triangulation: Can not create new triangles for inserted point p." );
    }
}

Test du code avec shared_ptr:

Le code a été réécrit sans aucune optimisation ...

void DT2D::DTInsertPoint ( std::shared_ptr <Point2D> p, std::shared_ptr <HalfEdge> *e1, HalfEdgesList * half_edges_dt )
{
    // One step of the Delaunay triangulation, incremental insertion by de Berg (2001)
    short   status = -1;

    //Pointers
    std::shared_ptr <HalfEdge> e31;
    std::shared_ptr <HalfEdge> e21;
    std::shared_ptr <HalfEdge> e12;
    std::shared_ptr <HalfEdge> e32;
    std::shared_ptr <HalfEdge> e23;
    std::shared_ptr <HalfEdge> e13;
    std::shared_ptr <HalfEdge> e53;
    std::shared_ptr <HalfEdge> e44;
    std::shared_ptr <HalfEdge> e63;

    try
    {
            // Test, if point lies inside triangle
            *e1 = LawsonOrientedWalk::findTriangleWalk ( p, &status, *e1, 0 );

            if ( e1 != NULL )
            {
                    // Edges inside triangle lies the point
                    std::shared_ptr <HalfEdge> e2((*e1 )->getNextEdge());
                    std::shared_ptr <HalfEdge> e3(e2->getNextEdge());

                    // Point lies inside the triangle
                    if ( status == 1 )
                    {
                            // Create first new triangle T1, twin edges set after creation
            e31.reset( new HalfEdge ( p, *e1, NULL ));
                            e21.reset( new HalfEdge ( e2->getPoint(), e31, NULL ));
                            ( *e1 )->setNextEdge ( e21 );

                            // Create second new triangle T2, twin edges set after creation
                            e12.reset( new HalfEdge ( p, e2, NULL ));
                            e32.reset( new HalfEdge ( e3->getPoint(), e12, NULL ));
                            e2->setNextEdge ( e32 );

                            // Create third new triangle T3, twin edges set after creation
                            e23.reset( new HalfEdge ( p, e3, NULL ));
                            e13.reset( new HalfEdge ( ( *e1 )->getPoint(), e23, NULL ));
                            e3->setNextEdge ( e13 );

                            // Set twin edges in T1, T2, T3
                            e12->setTwinEdge ( e21 );
                            e21->setTwinEdge ( e12 );
                            e13->setTwinEdge ( e31 );
                            e31->setTwinEdge ( e13 );
                            e23->setTwinEdge ( e32 );
                            e32->setTwinEdge ( e23 );

                            // Add new edges into list
                            half_edges_dt->Push_back ( e21 );
                            half_edges_dt->Push_back ( e12 );
                            half_edges_dt->Push_back ( e31 );
                            half_edges_dt->Push_back ( e13 );
                            half_edges_dt->Push_back ( e32 );
                            half_edges_dt->Push_back ( e23 );

                            // Legalize triangle T1
                            if ( ( *e1 )->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, *e1 );
                            }

                            // Legalize triangle T2
                            if ( e2->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e2 );
                            }

                            // Legalize triangle T3
                            if ( e3->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e3 );
                            }
                    }

                    // Point lies on the Edge of the triangle
                    else if ( status == 2 )
                    {
                            // Find adjacent triangle
                            std::shared_ptr <HalfEdge> e4 = ( *e1 )->getTwinEdge();
                            std::shared_ptr <HalfEdge> e5 = e4->getNextEdge();
                            std::shared_ptr <HalfEdge> e6 = e5->getNextEdge();

                            // Create first new triangle T1, twin edges set after creation
                            e21.reset(new HalfEdge ( p, e3, NULL ));
                            ( *e1 )->setNextEdge ( e21 );

                            // Create second new triangle T2, OK
                            e12.reset(new HalfEdge ( p, e2, e4 ));
                            e32.reset(new HalfEdge ( e3->getPoint(), e12, e21 ));
                            e2->setNextEdge ( e32 );

                            // Create third new triangle T3, twin edges set after creation
                            e53.reset(new HalfEdge ( p, e6, NULL ));
                            e4->setNextEdge ( e53 );

                            // Create fourth new triangle T4, OK
                            e44.reset(new HalfEdge ( p, e5, *e1 ));
                            e63.reset(new HalfEdge ( e6->getPoint(), e44, e53 ));
                            e5->setNextEdge ( e63 );

                            // Set twin edges in T1, T3
                            e21->setTwinEdge ( e32 );
                            ( *e1 )->setTwinEdge ( e44 );
                            e53->setTwinEdge ( e63 );
                            e4->setTwinEdge ( e12 );

                            // Add new edges into list
                            half_edges_dt->Push_back ( e21 );
                            half_edges_dt->Push_back ( e12 );
                            half_edges_dt->Push_back ( e32 );
                            half_edges_dt->Push_back ( e53 );
                            half_edges_dt->Push_back ( e63 );
                            half_edges_dt->Push_back ( e44 );

                            // Legalize triangle T1
                            if ( e3->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e3 );
                            }

                            // Legalize triangle T4
                            if ( e5->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e5 );
                            }

                            // Legalize triangle T3
                            if ( e6->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e6 );
                            }

                            // Legalize triangle T2
                            if ( e2->getTwinEdge() != NULL )
                            {
                                    legalizeTriangle ( p, e2 );
                            }
                    }
            }
    }
    //Throw exception
    catch ( std::bad_alloc &e )
    {
    /*
            //Free memory
            if ( e31 != NULL ) delete e31;
            if ( e21 != NULL ) delete e21;
            if ( e12 != NULL ) delete e12;
            if ( e32 != NULL ) delete e32;
            if ( e23 != NULL ) delete e23;
            if ( e13 != NULL ) delete e13;
            if ( e53 != NULL ) delete e53;
            if ( e44 != NULL ) delete e44;
            if ( e63 != NULL ) delete e63;
    */
            //Throw exception
            throw ErrorBadAlloc ( "EErrorBadAlloc: ", "Delaunay triangulation: Can not create new triangles for inserted point p." );
    }

    //Throw exception
    catch ( ErrorMathZeroDevision &e )
    {
    /*
            //Free memory
            if ( e31 != NULL ) delete e31;
            if ( e21 != NULL ) delete e21;
            if ( e12 != NULL ) delete e12;
            if ( e32 != NULL ) delete e32;
            if ( e23 != NULL ) delete e23;
            if ( e13 != NULL ) delete e13;
            if ( e53 != NULL ) delete e53;
            if ( e44 != NULL ) delete e44;
            if ( e63 != NULL ) delete e63;
    */
            //Throw exception
            throw ErrorBadAlloc ( "EErrorMathZeroDevision: ", "Delaunay triangulation: Can not create new triangles for inserted point p." );
    }
}

Merci de votre aide...

Éditer

J'ai remplacé le passage direct de tous les objets par le passage d'alias &. Les constructeurs de copie sont utilisés moins fréquemment qu'avant.

Tables mises à jour pour shared_ptr

shared_ptr (C++ 0x00) ancien:

N[points]         t[sec]     
100 000                6   
200 000               11   
300 000               16    
900 000               36   

shared_ptr (C++ 0x00) nouvelle version:

N[points]         t[sec]      
100 000                2  
200 000                5  
300 000                9  
900 000               24  

Il y a une amélioration considérable, mais la version shared_ptr est toujours 4 fois plus lente que celle du pointeur brut. Je crains que la vitesse d'exécution du programme ne puisse être augmentée de manière significative.

48
Ian

shared_ptr Sont le type de pointeur le plus compliqué jamais créé:

  • Le comptage des références prend du temps
  • Allocation multiple (il y a 3 parties: l'objet, le compteur, le deleter)
  • Un certain nombre de méthodes virtuelles (dans le compteur et le suppresseur) pour l'effacement de type
  • Fonctionne entre plusieurs threads (donc la synchronisation)

Il y a 2 façons de les rendre plus rapides:

  • utilisez make_shared pour les allouer , car (malheureusement ) le constructeur normal alloue deux blocs différents: un pour l'objet et un pour le compteur et le deleter.
  • ne les copiez pas si vous n'en avez pas besoin: les méthodes doivent accepter shared_ptr<T> const&

Mais il existe également de nombreuses façons de NE PAS les utiliser.

En regardant votre code, il semble que vous fassiez BEAUCOUP d'allocation de mémoire, et je ne peux m'empêcher de me demander si vous ne pouvez pas trouver une meilleure stratégie. Je dois admettre que je n'ai pas obtenu le chiffre complet, donc je vais peut-être me diriger directement vers un mur, mais ...

Le code est généralement beaucoup plus simple si vous avez un propriétaire pour chacun des objets. Par conséquent, shared_ptr Devrait être une mesure de dernier recours, utilisée lorsque vous ne pouvez pas obtenir un seul propriétaire.

Quoi qu'il en soit, nous comparons des pommes et des oranges ici, le code d'origine est buggé. Vous vous occupez de deleting de la mémoire (bonne) mais vous avez oublié que ces objets étaient également référencés à partir d'autres points du programme e1->setNextEdge(e21) qui contient maintenant des pointeurs vers des objets détruits (dans un zone mémoire). Par conséquent, je suppose qu'en cas d'exception, vous effacez simplement la liste entière? (Ou en quelque sorte parier sur un comportement indéfini pour jouer à Nice)

Il est donc difficile de juger des performances car le premier ne se remet pas bien des exceptions tandis que le second le fait.

Enfin: Avez-vous pensé à utiliser intrusive_ptr ? Cela pourrait vous donner un coup de pouce (hehe) si vous ne les synchronisez pas (un seul thread) et vous éviteriez beaucoup de choses effectuées par le shared_ptr Ainsi qu'un gain sur la localité de référence.

75
Matthieu M.

Je recommande toujours d'utiliser std :: shared_ptr <> au lieu de compter sur la gestion manuelle de la durée de vie de la mémoire. Cependant, la gestion automatique de la durée de vie coûte quelque chose mais n'est généralement pas significative.

Dans votre cas, vous avez remarqué que shared_ptr <> est important et comme certains l'ont dit, vous devez vous assurer de ne pas copier inutilement un pointeur partagé car cela force une addref/release.

Mais il y a une autre question en arrière-plan: avez-vous vraiment besoin de compter sur new/delete en premier lieu? new/delete utilise malloc/free qui n'est pas réglé pour les allocations de petits objets.

Une bibliothèque qui m'a beaucoup aidé auparavant est boost :: object_pool .

À un moment donné, j'ai voulu créer des graphiques très rapidement. Les nœuds et les bords sont naturellement alloués dynamiquement et j'en tire deux coûts.

  1. malloc/gratuit
  2. Gestion de la durée de vie de la mémoire

boost: object_pool permet de réduire ces deux coûts au prix de ne pas être aussi général que malloc/free.

Donc, à titre d'exemple, disons que nous avons un nœud simple comme celui-ci:

   struct node
   {
      node * left;
      node * right;
   };

Donc, au lieu d'un nœud d'allocation avec un nouveau, j'utilise boost :: object_pool. Mais boost :: object_pool suit également toutes les instances allouées avec lui, donc à la fin de mon calcul, j'ai détruit object_pool et je n'avais pas besoin de suivre chaque nœud, simplifiant ainsi mon code et améliorant les performances.

J'ai fait quelques tests de performances (j'ai écrit ma propre classe de pool juste pour le plaisir mais bool :: object_pool devrait donner les mêmes performances ou mieux).

10 000 000 nœuds créés et détruits

  1. Plain new/delete: 2.5secs
  2. shared_ptr: 5secs
  3. boost :: object_pool: 0.15secs

Donc, si boost :: object_pool fonctionne pour vous, cela pourrait aider à réduire considérablement la surcharge d'allocation de mémoire.

Par défaut, si vous créez vos pointeurs partagés de manière naïve (c'est-à-dire shared_ptr<type> p( new type )) vous encourez deux allocations de mémoire, une pour l'objet réel et une allocation supplémentaire pour le compte de référence. Vous pouvez éviter l'allocation supplémentaire en utilisant le make_shared modèle qui effectuera une instanciation unique pour l'objet et le nombre de références, puis construira sur place l'objet.

Le reste des coûts supplémentaires sont assez faibles par rapport au doublement des appels à malloc, comme l'incrémentation et la décrémentation du décompte (les deux opérations atomiques) et les tests de suppression. Si vous pouvez fournir du code sur la façon dont vous utilisez les pointeurs/pointeurs partagés, vous pourriez avoir une meilleure idée de ce qui se passe réellement dans le code.

Essayez-le en mode "release" et voyez si vous vous rapprochez des repères. Le mode débogage a tendance à activer de nombreuses assertions dans la STL, ce qui ralentit beaucoup de choses.

7
Ken Simon

shared_ptrsont sensiblement plus lents que les pointeurs bruts. C'est pourquoi ils ne devraient être utilisés que si vous besoin sémantique de propriété partagée.

Sinon, plusieurs autres types de pointeurs intelligents sont disponibles. scoped_ptr et auto_ptr (C++ 03) ou unique_ptr (C++ 0x) ont tous deux leur utilité. Et souvent, la meilleure solution est de ne pas utiliser de pointeur d'aucune sorte, et d'écrire simplement votre propre classe RAII à la place.

UNE shared_ptr doit incrémenter/décrémenter/lire le compteur de référence et, en fonction de l'implémentation et de la façon dont il est instancié, le compteur ref peut être alloué séparément, ce qui peut entraîner des échecs de cache potentiels. Et il doit accéder au compteur ref atomiquement, ce qui ajoute une surcharge supplémentaire.

6
jalf

Il est impossible de répondre à cela sans plus de données. Avez-vous profilé le code pour identifier avec précision la source du ralentissement dans la version shared_ptr? L'utilisation du conteneur ajoutera certainement des frais généraux, mais je serais surpris s'il le rend 10 fois plus lent.

VSTS dispose de bons outils de perf qui attribueront l'utilisation du processeur exactement si vous pouvez l'exécuter pendant environ 30 secondes. Si vous n'avez pas accès à VS Performance Tools ou à un autre jeu d'outils de profilage, exécutez le code shared_ptr dans le débogueur et entrez-le 10 ou 15 fois pour obtenir un échantillon de force brute de l'endroit où il passe tout son temps. C'est étonnamment et contre-intuitivement efficace, j'ai trouvé.

[EDIT] Ne passez pas votre shared_ptr par valeur dans cette variante du code - utilisez ref pour const. Si cette fonction est souvent appelée, cela aura un impact mesurable.

2
Steve Townsend

C'est lent car il utilise pour référence les instructions atomiques inc/dec, donc c'est horriblement lent. Si vous avez vraiment besoin de GC en C++, n'utilisez pas naïf RF GC et utilisez une stratégie RC plus développée, ou trace GC. http://www.hboehm.info/ gc / est agréable pour ne pas accélérer les tâches critiques (mais beaucoup mieux que les "pointeurs intelligents" RC naïfs).

1
dev1223