shared_ptr:可怕的速度

时间:2010-09-02 14:27:22

标签: c++ performance compiler-construction shared-ptr

当比较指针的两个变体 - 经典与shared_ptr时 - 我对程序运行速度的显着提高感到惊讶。为了测试2D Delaunay增量插入算法已被使用。

编译器设置:

  

VS 2010(发布)/ O2 / MD / GL,W7教授,CPU 3.GHZ DualCore

结果:

shared_ptr(C ++ 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倍。这是由编译器设置引起的还是C ++ 0x00 shared_ptr实现那么慢?

VS2010 Profiler:对于原始指针,大约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." );
    }
}

感谢您的帮助......

修改

我用别名传递&amp;替换了所有对象的直接传递。复制构造函数的使用频率低于之前。

更新了shared_ptr

的表格

shared_ptr(C ++ 0x00)old:

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

shared_ptr(C ++ 0x00)新版本:

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

有一个相当大的改进,但shared_ptr版本仍然比原始指针一慢4倍。我担心程序的运行速度不会显着提高。

7 个答案:

答案 0 :(得分:75)

shared_ptr是有史以来最复杂的指针类型:

  • 参考计数需要时间
  • 多个分配(有3个部分:对象,计数器,删除器)
  • 用于类型擦除的许多虚拟方法(在计数器和删除器中)
  • 在多个线程之间工作(因此同步)

有两种方法可以让它们更快:

  • 使用 make_shared 分配,因为(不幸的是)普通构造函数分配了两个不同的块:一个用于对象,一个用于计数器和删除器
  • 不要复制如果您不需要:方法应该接受shared_ptr<T> const&

但也有很多方法不使用它们。

看看你的代码看起来你做了大量的内存分配,我不禁想知道你是否找不到更好的策略。我必须承认我没有得到完整的数字,所以我可能会直奔墙壁但是......

如果您拥有每个对象的所有者,通常代码会更简单。因此,shared_ptr应该是最后的手段,在您无法获得单一所有者时使用。

无论如何,我们在这里比较苹果和橘子,原始代码是错误的。你需要处理deleting内存(好),但是你忘了这些对象也是从程序e1->setNextEdge(e21)中的其他点引用的,它们现在保存了对被破坏对象的指针(在free'd内存区域中) 。因此,我想如果出现异常,你只需要清除整个列表? (或以某种方式押注未定义的行为以发挥不错)

所以很难判断表现,因为前者不会从例外情况中恢复得很好。

最后:您是否考虑过使用intrusive_ptr?如果你不同步它们(单线程)它可以给你一些提升(hehe)你会避免shared_ptr执行的很多东西以及获得引用的位置。

答案 1 :(得分:22)

我总是建议使用std :: shared_ptr&lt;&gt;而不是依赖于手动内存生命周期管理。但是,自动终身管理需要花费一些成本,但通常并不重要。

在您的情况下,您注意到shared_ptr&lt;&gt;是重要的,正如一些人所说,你应该确保你没有不必要地复制共享指针,因为强制addref / release。

但是背景中还有另一个问题:你真的需要首先依赖新/删除吗? new / delete使用malloc / free,它们没有针对小对象的分配进行调整。

之前帮助我的图书馆是boost::object_pool

在某个阶段我想快速创建图表。节点和边缘自然是动态分配的,我这样做会产生两个成本。

  1. 的malloc /免费
  2. 内存生存期管理
  3. 提升:object_pool有助于降低这些成本,但代价是不像malloc / free那样普遍。

    举个例子,假设我们有一个这样的简单节点:

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

    因此,使用boost :: object_pool代替使用new的分配节点。但是boost :: object_pool也会跟踪所有分配的实例,所以在计算结束时我销毁了object_pool,不需要跟踪每个节点,从而简化了代码并提高了性能。

    我做了一些性能测试(我编写自己的池类只是为了好玩,但是bool :: object_pool应该提供相同的性能或更好)。

    创建并销毁了10,000,000个节点

    1. 普通新/删除:2.5秒
    2. shared_ptr:5secs
    3. boost :: object_pool:0.15secs
    4. 因此,如果boost :: object_pool适合您,则可能有助于显着降低内存分配开销。

答案 2 :(得分:12)

默认情况下,如果以天真的方式创建共享指针(即shared_ptr<type> p( new type )),则会产生两个内存分配,一个用于实际对象,另一个用于引用计数。您可以通过使用make_shared模板来避免额外的分配,该模板将对对象和引用计数执行单个实例化,然后就地构造对象。

与对malloc的调用加倍相比,其余的额外成本非常小,例如递增和递减计数(两个原子操作)和测试删除。如果您可以提供一些使用指针/共享指针的代码,那么您可以更好地了解代码中实际发生的情况。

答案 3 :(得分:7)

在“发布”模式下试用它,看看你是否接近基准测试。调试模式倾向于在S​​TL中打开许多断言,这会使许多事情变慢。

答案 4 :(得分:6)

shared_ptr 明显慢于原始指针。这就是为什么只有在你真正需要共享所有权语义时才能使用它们。

否则,还有其他几种智能指针类型可用。 scoped_ptrauto_ptr(C ++ 03)或unique_ptr(C ++ 0x)都有其用途。通常,最好的解决方案是不使用任何类型的指针,而只是编写自己的RAII类。

shared_ptr必须递增/递减/读取参考计数器,并且取决于实现以及如何实例化,可以单独分配ref计数器,从而导致潜在的高速缓存未命中。它必须以原子方式访问ref计数器,这会增加额外的开销。

答案 5 :(得分:2)

如果没有更多数据,就无法回答这个问题。您是否已分析代码以准确识别shared_ptr版本中减速的来源?使用容器肯定会增加开销,但如果速度慢10倍,我会感到惊讶。

VSTS有很好的perf工具,如果你可以运行30秒左右,它将准确地归因于CPU的使用。如果您无法访问VS性能工具或其他性能分析工具集,则在调试器中运行shared_ptr代码并将其分解10或15次,以获得其花费所有时间的蛮力样本。我发现这令人惊讶且反直觉有效。

[编辑]不要在代码的变体中通过值传递您的shared_ptr - 使用ref to const。如果这个函数被大量调用,那将会产生可衡量的影响。

答案 6 :(得分:1)

它很慢,因为它用于引用inc / dec操作原子指令,因此它很慢。如果您真的需要C ++中的GC,请不要使用天真的RF GC并使用一些更加开发的RC策略或跟踪GC。 http://www.hboehm.info/gc/对于不是速度关键任务很好(但比“智能指针”天真的RC要好很多。