web-dev-qa-db-ja.com

shared_ptr:恐ろしい速度

ポインタの2つのバリアント-クラシックポインタとshared_ptrを比較すると、プログラムの実行速度が大幅に向上したことに驚きました。 2D Delaunayの増分挿入アルゴリズムをテストするために使用されています。

コンパイラー設定:

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%が費やされます(それは問題ありません、それは周知の事実です)。ただし、shared_ptrバージョンの場合、shared_ptr.reset()を使用すると時間の約58%が消費され、ヒューリスティック検索には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." );
    }
}

ご協力いただきありがとうございます...

編集

すべてのオブジェクトの直接受け渡しを、別名受け渡し&に置き換えました。コピーコンストラクターの使用頻度は以前よりも低くなりました。

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バージョンは未加工のポインターバージョンよりも4倍遅いです。プログラムの実行速度を大幅に上げることはできないと思います。

48
Ian

_shared_ptr_は、これまでで最も複雑なタイプのポインターです。

  • 参照カウントには時間がかかります
  • 複数の割り当て(3つの部分があります:オブジェクト、カウンター、削除者)
  • タイプ消去のための多数の仮想メソッド(カウンターおよび削除者内)
  • 複数のスレッド間で動作します(したがって、同期)

それらを高速化する2つの方法があります。

  • use_make_shared_それらを割り当てるために、なぜなら(残念ながら)通常のコンストラクターは2つの異なるブロックを割り当てます。1つはオブジェクト用、もう1つはカウンターと削除用です。
  • それらをコピーしないでください必要がない場合:メソッドは_shared_ptr<T> const&_を受け入れる必要があります

しかし、それらを使用しない多くの方法もあります。

あなたのコードを見ると、あなたはたくさんのメモリ割り当てをしているように見えます、そして私はあなたがより良い戦略を見つけることができなかったかどうか疑問に思わずにはいられません。私は完全な体型を持っていなかったことを認めなければならないので、私は壁にまっすぐ向かうかもしれませんが...

通常、各オブジェクトの所有者がいる場合、コードははるかに単純です。したがって、_shared_ptr_は最後の手段であり、単一の所有者を取得できない場合に使用する必要があります。

とにかく、ここではリンゴとオレンジを比較していますが、元のコードにはバグがあります。 deletingのメモリを管理します(良い)が、これらのオブジェクトはプログラムの別のポイントからも参照されていることを忘れていましたe1->setNextEdge(e21)は破壊されたオブジェクトへのポインタを保持しますメモリゾーン)。したがって、例外の場合は、リスト全体を消去するだけだと思いますか? (またはニースをプレイするために未定義の動作に何らかの形で賭けます)

そのため、前者は例外からうまく回復できず、後者は回復するため、パフォーマンスを判断するのは困難です。

最後に: intrusive_ptr の使用を考えましたか?それらを同期しない場合(単一スレッド)、ブースト(hehe)が得られ、_shared_ptr_によって実行される多くの処理を回避し、参照の局所性を得ることができます。

75
Matthieu M.

手動のメモリライフタイム管理に頼るのではなく、std :: shared_ptr <>を使用することを常にお勧めします。ただし、自動ライフタイム管理にはいくらか費用がかかりますが、通常は重要ではありません。

あなたの場合、shared_ptr <>が重要であることに気づきました。また、一部の人が言ったように、addref/releaseを強制するために共有ポインタを不必要にコピーしないように注意する必要があります。

しかし、バックグラウンドには別の質問があります。そもそも本当に、新規/削除に依存する必要があるのでしょうか? new/deleteは、小さなオブジェクトの割り当て用に調整されていないmalloc/freeを使用します。

以前に私を助けてくれたライブラリは boost :: object_pool です。

ある段階で、グラフを非常に高速に作成したかったのです。ノードとエッジは自然に動的に割り当てられ、そのために2つのコストが発生します。

  1. malloc/free
  2. メモリ寿命管理

boost:object_poolは、これらの両方のコストを削減しますが、malloc/freeほど一般的ではありません。

例として、次のような単純なノードがあるとしましょう。

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

したがって、newの割り当てノードの代わりに、boost :: object_poolを使用します。ただし、boost :: object_poolは、割り当てられたすべてのインスタンスも追跡するため、計算の最後にobject_poolを破棄し、各ノードを追跡する必要がなかったため、コードが簡素化され、パフォーマンスが向上します。

いくつかのパフォーマンステストを行いました(楽しみのために独自のプールクラスを作成しましたが、bool :: object_poolは同じパフォーマンス以上を提供するはずです)。

10,000,000個のノードが作成および破棄されました

  1. 単純な新規/削除:2.5秒
  2. shared_ptr:5秒
  3. boost :: object_pool:0.15秒

したがって、boost :: object_poolが機能する場合、メモリ割り当てのオーバーヘッドを大幅に削減できます。

デフォルトでは、共有ポインターを単純な方法で作成した場合(つまり、shared_ptr<type> p( new type ))、実際のオブジェクト用と参照カウント用の追加の割り当ての2つのメモリ割り当てが発生します。オブジェクトと参照カウントの両方に対して単一のインスタンス化を実行し、その場でオブジェクトを構築するmake_sharedテンプレートを使用することにより、余分な割り当てを回避できます。

残りの追加コストは、カウントのインクリメントとデクリメント(両方のアトミック操作)や削除のテストなど、mallocの呼び出しを2倍にするのに比べて非常に小さくなります。ポインター/共有ポインターをどのように使用しているかでコードを提供できる場合、コードで実際に何が行われているかについてより良い洞察を得ることができます。

「リリース」モードで試して、より近いベンチマークを取得するかどうかを確認してください。デバッグモードでは、STLで多くのアサーションがオンになり、多くの処理が遅くなります。

7
Ken Simon

shared_ptrare生のポインタよりも著しく遅い。そのため、実際に必要共有所有権セマンティクスを使用する場合にのみ使用する必要があります。

それ以外の場合は、他のいくつかの種類のスマートポインターを使用できます。 scoped_ptrおよびauto_ptr(C++ 03)またはunique_ptr(C++ 0x)には両方の用途があります。多くの場合、最良の解決策は、いかなる種類のポインターも使用せず、代わりに独自のRAIIクラスを作成することです。

shared_ptrは、参照カウンタをインクリメント/デクリメント/読み取りする必要があります。実装とインスタンス化方法によっては、refカウンタが個別に割り当てられ、キャッシュミスが発生する可能性があります。また、refカウンターにアトミックにアクセスする必要があるため、オーバーヘッドが追加されます。

6
jalf

これ以上のデータなしでこれに答えることは不可能です。 shared_ptrバージョンのスローダウンの原因を正確に特定するために、コードのプロファイルを作成しましたか?コンテナを使用すると確かにオーバーヘッドが増加しますが、10倍遅くなると驚くでしょう。

VSTSには、これを30秒程度実行できる場合にCPU使用率を正確に特定するニースperfツールがあります。 VS Performance Toolsまたは他のプロファイリングツールセットにアクセスできない場合は、デバッガーでshared_ptrコードを実行し、10回または15回侵入して、すべての時間を費やしている場所のブルートフォースサンプルを取得します。これは驚くほど直感に反して効果的であることがわかりました。

[編集]コードのそのバリアントでは、値によってshared_ptrを渡さないでください。constにrefを使用してください。この関数が頻繁に呼び出されると、測定可能な-veの影響があります。

2
Steve Townsend

参照のinc/dec操作にアトミック命令を使用するため低速であり、ひどい低速です。 C++でGCが本当に必要な場合は、単純なRF GCを使用して、さらに開発されたRC戦略またはGCのトレースを使用しないでください。 http://www.hboehm.info/ gc / は、重要なタスクを高速化しない場合に適しています(ただし、「スマートポインター」ナイーブRCよりもはるかに優れています)。

1
dev1223