Shared_ptr: ужасная скорость

При сравнении двух вариантов указателей: классический или shared_ptr &mdash, я был удивлен значительным увеличением скорости работы программы. Для тестирования 2D Delaunay был применен инкрементный алгоритм Вставки.

Настройки компилятора:

VS 2010 (выпуск)/O2/MD/GL, W7 Prof, CPU 3.GHZ DualCore

Результаты:

shared_ptr (С++ 0x00):

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

Указатели

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

Время выполнения версий shared_ptr примерно в 10 раз больше. Это вызвано параметрами компилятора или реализацией С++ 0x00 shared_ptr так медленно?

VS2010 Профилировщик: для необработанных указателей около 60% времени тратится на эвристический поиск треугольника, содержащего вставленную точку (это хорошо, это общеизвестный факт). Но для версии shared_ptr около 58% времени тратится с использованием shared_ptr.reset(), и только 10% используется для эвристического поиска.

Код проверки с необработанными указателями:

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;
    }

Процедура точки вставки:

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." );
    }
}

Тестирование кода с помощью shared_ptr:

Код был переписан без какой-либо оптимизации...

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." );
    }
}

Спасибо за вашу помощь...

Изменить

Я заменил прямую передачу всех объектов с помощью прохода alias &. Копировать конструкторы используются менее часто, чем раньше.

Обновлены таблицы для shared_ptr

shared_ptr (С++ 0x00) old:

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

shared_ptr (С++ 0x00) новая версия:

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

Существует значительное улучшение, но версия shared_ptr все еще в 4 раза медленнее, чем исходный указатель. Я боюсь, что скорость работы программы не может быть значительно увеличена.

Ответ 1

shared_ptr - самый сложный тип указателя:

  • Подсчет ссылок занимает время
  • Множественное распределение (есть 3 части: объект, счетчик, дебетер)
  • Ряд виртуальных методов (в счетчике и удалении) для стирания типа
  • Работает между несколькими потоками (таким образом, синхронизация)

Есть 2 способа сделать их быстрее:

  • используйте make_shared , чтобы выделить их, потому что (к сожалению) нормальный конструктор выделяет два разных блока: один для объекта и один для счетчика и дебетер.
  • не копируйте их, если вам не нужно: методы должны принимать shared_ptr<T> const&

Но есть и много способов НЕ использовать их.

Глядя на ваш код, похоже, что вы делаете много места для распределения памяти, и я не могу не задаться вопросом, не найти ли вы лучшую стратегию. Должен признаться, что у меня не было полной фигуры, поэтому я, возможно, направляюсь прямо к стене, но...

Обычно код намного проще, если у вас есть владелец для каждого из объектов. Поэтому shared_ptr должен быть последней мерой, применяемой, когда вы не можете получить ни одного владельца.

В любом случае, мы сравниваем яблоки и апельсины здесь, исходный код багги. Вы заботитесь о deleting в памяти (хорошо), но вы забыли, что на эти объекты также ссылались другие точки в программе e1->setNextEdge(e21), которая теперь содержит указатели на разрушенные объекты (в свободной зоне памяти). Поэтому я предполагаю, что в случае исключения вы просто уничтожили весь список? (Или как-то ставку на undefined поведение, чтобы играть хорошо)

Так трудно судить о спектаклях, так как первый не восстанавливается из-за исключений, пока последний делает.

Наконец: Вы думали об использовании intrusive_ptr? Это может привести к некоторому усилению (хе-хе), если вы не синхронизируете их (один поток), и вы избегаете большого количества вещей, выполняемых shared_ptr, а также выигрыша по локальности ссылки.

Ответ 2

Я всегда рекомендую использовать std:: shared_ptr < > вместо того, чтобы полагаться на управление временем работы вручную. Однако автоматическое управление жизненным циклом требует чего-то, но обычно не имеет значения.

В вашем случае вы заметили, что shared_ptr < > является значительным, и, как утверждают некоторые, вы должны убедиться, что вы не произвольно копируете общий указатель, поскольку это принудительно создает addref/release.

Но есть еще один вопрос в фоновом режиме: действительно ли вам нужно полагаться на новое/удалить в первую очередь? new/delete использует malloc/free, которые не настроены для выделения небольших объектов.

Библиотека, которая мне помогла, - boost:: object_pool.

На каком-то этапе я хотел очень быстро создавать графики. Естественно динамически распределяются узлы и ребра, и я получаю две издержки от этого.

  • таНос/свободный
  • Управление жизненным циклом памяти

boost: object_pool помогает снизить обе эти затраты за счет того, что они не являются такими же общими, как malloc/free.

Итак, в качестве примера предположим, что у нас есть простой node:

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

Поэтому вместо выделения node с новым я использую boost:: object_pool. Но boost:: object_pool также отслеживает все экземпляры, выделенные им, поэтому в конце моего расчета я уничтожил object_pool и не нуждался в отслеживании каждого node, что упростило мой код и улучшило производительность.

Я провел некоторое тестирование производительности (я написал свой собственный класс пула только для удовольствия, но bool:: object_pool должен давать такую ​​же производительность или лучше).

Создано и уничтожено 10 000 000 узлов

  • Обычный новый/удалить: 2.5 сек.
  • shared_ptr: 5 секунд
  • boost:: object_pool: 0.15secs

Итак, если boost:: object_pool работает для вас, это может значительно сократить значительную часть распределения памяти.

Ответ 3

По умолчанию, если вы создаете свои общие указатели наивным способом (т.е. shared_ptr<type> p( new type )), вы берете два распределения памяти, один для фактического объекта и дополнительное выделение для счетчика ссылок. Вы можете избежать дополнительного распределения, используя шаблон make_shared, который будет выполнять одно экземпляр как для объекта, так и для счетчика ссылок, а затем для создания объекта на месте.

Остальные дополнительные затраты довольно малы по сравнению с удвоением вызовов в malloc, такими как увеличение и уменьшение счета (как для атомных операций), так и тестирование для удаления. Если вы можете предоставить некоторый код в том, как вы используете указатели/общие указатели, вы можете получить более полное представление о том, что на самом деле происходит в коде.

Ответ 4

Попробуйте в режиме "выпуска" и посмотрите, приближаетесь ли вы к более тесным критериям. Режим отладки имеет тенденцию включать множество утверждений в STL, которые замедляют множество вещей.

Ответ 5

shared_ptr заметно медленнее, чем исходные указатели. Вот почему они должны использоваться только в том случае, если вам действительно нужна совместная семантика собственности.

В противном случае доступно несколько других типов интеллектуальных указателей. scoped_ptr и auto_ptr (С++ 03) или unique_ptr (С++ 0x) оба имеют свои применения. И часто лучшим решением является не использование какого-либо указателя, а просто запись собственного класса RAII.

A shared_ptr должен увеличивать/уменьшать/считывать контрольный счетчик, и в зависимости от реализации и того, как она создается, счетчик ссылок может быть выделен отдельно, что вызывает потенциальные промахи в кэше. И он должен получить доступ к счетчику ref атомарно, что добавляет дополнительные накладные расходы.

Ответ 6

Невозможно ответить на это без дополнительных данных. Профилировали ли вы код для точного определения источника замедления в версии shared_ptr? Использование контейнера, безусловно, добавит накладные расходы, но я был бы удивлен, если он сделает его на 10 раз медленнее.

У VSTS есть приятные перфомансы, которые будут приписывать использование ЦП именно в том случае, если вы можете запустить это в течение 30 секунд или около того. Если у вас нет доступа к инструментам производительности VS или другому набору инструментов профилирования, запустите код shared_ptr в отладчике и прорвется на него 10 или 15 раз, чтобы получить образец грубой силы, где он проводит все свое время. Это удивительно и контр-интуитивно эффективно, я нашел.

[EDIT] Не передавайте свой shared_ptr по значению в этом варианте кода - используйте ref для const. Если эта функция называется много, это будет иметь измеримое влияние.

Ответ 7

Он медленный, потому что он использует для справки inc/dec операции атомарные инструкции, поэтому он очень медленный. Если вам действительно нужен GC на С++, не используйте наивный RF GC и используйте более развитую RC-стратегию или трассировку GC. http://www.hboehm.info/gc/ хорош для критических задач без скорости (но намного лучше, чем "умные указатели" наивного RC).