shared_ptr:可怕的速度

当比较指针的两个变种 – classic vs. shared_ptr时,我对程序运行速度的显着提高感到惊讶。 用于testing二维Delaunay增量插入algorithm已被使用。

编译器设置:

VS 2010(发布)/ O2 / MD / GL,W7 Prof,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探查器:对于原始指针,大约60%的时间是通过启发式search包含插入点的三angular形花费的(这是可以的,这是一个众所周知的事实)。 但是对于shared_ptr版本,大约58%的时间花在使用shared_ptr.reset()上,只有10%用于启发式search。

用原始指针testing代码:

 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_ptrtesting代码:

代码被重写,没有任何优化…

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

谢谢你的帮助…

编辑

我用直接传递的所有对象与别名传递&。 复制构造函数使用频率较低。

更新了shared_ptr的表格

shared_ptr(C ++ 0x00)旧:

 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版本仍然比原始指针慢一倍。 恐怕程序的运行速度不能显着提高。

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

  • 参考计数需要时间
  • 多重分配(有三部分:对象,计数器,删除器)
  • 一些虚拟方法(在计数器和删除器)进行types擦除
  • 在多个线程之间工作(因此同步)

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

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

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

看看你的代码,看起来你做了很多的内存分配,我不禁想知道你是否找不到更好的策略。 我必须承认我没有得到完整的数字,所以我可能会直接进入一堵墙,但…

通常情况下,如果每个对象都拥有一个所有者,代码就简单多了。 因此, shared_ptr应该是最后一招,当你无法得到一个单一的所有者。

无论如何,我们在这里比较苹果和橘子,原来的代码是越野车。 你注意deleting内存(好),但是你忘记了这些对象也是从程序e1->setNextEdge(e21)中的其他点引用的,它现在保存着指向被破坏对象的指针(在一个空闲的内存区域)。 因此,我猜如果是例外情况,你只需要清除整个列表? (或者以某种方式打赌未定义的行为打好)

所以很难判断performance,因为前者在例外情况下不能很好的恢复。

最后:你有没有想过使用intrusive_ptr ? 如果你不同步它们(单线程),它可以给你一些提升(hehe),你可以避免shared_ptr执行的很多东西,也可以获得引用的局部性。

我总是build议使用std :: shared_ptr <>而不是依靠手动内存生命周期pipe理。 但是,自动生命周期pipe理的成本有些但通常并不显着。

在你的情况下,你注意到shared_ptr <>是重要的,正如一些人说,你应该确保你不会不必要地复制一个共享的指针,因为这强制addref /释放。

但是在后台还有另外一个问题:你真的需要首先依靠新的/删除吗? 新/删除使用malloc / free这是不调整分配的小对象。

以前帮过我很多的库是boost :: object_pool 。

在某个阶段,我想创build非常快的graphics。 节点和边缘自然是dynamic分配的,我从中得到两个成本。

  1. 的malloc /免费
  2. 内存使用期限pipe理

boost:object_pool有助于降低这些成本,但不会像malloc / free那样普遍。

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

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

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

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

10,000,000个节点被创build和销毁

  1. 平原新/删除:2.5secs
  2. shared_ptr:5secs
  3. boost :: object_pool:0.15secs

所以如果boost :: object_pool为你工作,这可能有助于显着减less内存分配开销。

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

剩下的额外成本相对于将malloc调用加倍,比如递增和递减计数(包括primefaces操作)和testing删除都相当小。 如果你可以在你如何使用指针/共享指针的时候提供一些代码,你可以更好地了解代码中究竟发生了什么。

尝试在“释放”模式,看看你是否接近基准。 debugging模式往往会在STL中引发大量的断言,从而减缓了很多事情的发生。

shared_ptr明显比原始指针慢。 这就是为什么只有在实际需要共享所有权语义的情况下才能使用它们。

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

shared_ptr必须递增/递减/读取引用计数器,并且根据实现以及如何实例化,ref计数器可以分开分配,导致潜在的caching未命中。 它必须以primefaces方式访问ref计数器,这会增加额外的开销。

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

VSTS有很好的perf工具,如果你能运行这个工具30秒左右,就会精确地指定CPU使用率。 如果您无法访问VS性能工具或其他性能分析工具集,则可以在debugging器中运行shared_ptr代码并分解10次或15次,以获取花费所有时间的暴力破解样本。 我发现,这是令人惊讶的,反直觉有效的。

[编辑]不要通过你的shared_ptr值在该代码的变种 – 使用ref到const。 如果这个function被称为很多,这将有可测量的影响。

这很慢,因为它用于参考inc / dec操作primefaces指令,因此它是慢得可怕的。 如果您确实需要C ++中的GC,请勿使用简单的RF GC,并使用一些更加开发的RC策略或跟踪GC。 http://www.hboehm.info/gc/对于不加速关键任务是很好的(但比“智能指针”天真的RC好很多)。;