ポインタの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." );
}
}
コードは最適化なしで書き直されました...
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倍遅いです。プログラムの実行速度を大幅に上げることはできないと思います。
_shared_ptr
_は、これまでで最も複雑なタイプのポインターです。
それらを高速化する2つの方法があります。
make_shared
_それらを割り当てるために、なぜなら(残念ながら)通常のコンストラクターは2つの異なるブロックを割り当てます。1つはオブジェクト用、もう1つはカウンターと削除用です。shared_ptr<T> const&
_を受け入れる必要がありますしかし、それらを使用しない多くの方法もあります。
あなたのコードを見ると、あなたはたくさんのメモリ割り当てをしているように見えます、そして私はあなたがより良い戦略を見つけることができなかったかどうか疑問に思わずにはいられません。私は完全な体型を持っていなかったことを認めなければならないので、私は壁にまっすぐ向かうかもしれませんが...
通常、各オブジェクトの所有者がいる場合、コードははるかに単純です。したがって、_shared_ptr
_は最後の手段であり、単一の所有者を取得できない場合に使用する必要があります。
とにかく、ここではリンゴとオレンジを比較していますが、元のコードにはバグがあります。 deleting
のメモリを管理します(良い)が、これらのオブジェクトはプログラムの別のポイントからも参照されていることを忘れていましたe1->setNextEdge(e21)
は破壊されたオブジェクトへのポインタを保持しますメモリゾーン)。したがって、例外の場合は、リスト全体を消去するだけだと思いますか? (またはニースをプレイするために未定義の動作に何らかの形で賭けます)
そのため、前者は例外からうまく回復できず、後者は回復するため、パフォーマンスを判断するのは困難です。
最後に: intrusive_ptr の使用を考えましたか?それらを同期しない場合(単一スレッド)、ブースト(hehe)が得られ、_shared_ptr
_によって実行される多くの処理を回避し、参照の局所性を得ることができます。
手動のメモリライフタイム管理に頼るのではなく、std :: shared_ptr <>を使用することを常にお勧めします。ただし、自動ライフタイム管理にはいくらか費用がかかりますが、通常は重要ではありません。
あなたの場合、shared_ptr <>が重要であることに気づきました。また、一部の人が言ったように、addref/releaseを強制するために共有ポインタを不必要にコピーしないように注意する必要があります。
しかし、バックグラウンドには別の質問があります。そもそも本当に、新規/削除に依存する必要があるのでしょうか? new/deleteは、小さなオブジェクトの割り当て用に調整されていないmalloc/freeを使用します。
以前に私を助けてくれたライブラリは boost :: object_pool です。
ある段階で、グラフを非常に高速に作成したかったのです。ノードとエッジは自然に動的に割り当てられ、そのために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個のノードが作成および破棄されました
したがって、boost :: object_poolが機能する場合、メモリ割り当てのオーバーヘッドを大幅に削減できます。
デフォルトでは、共有ポインターを単純な方法で作成した場合(つまり、shared_ptr<type> p( new type )
)、実際のオブジェクト用と参照カウント用の追加の割り当ての2つのメモリ割り当てが発生します。オブジェクトと参照カウントの両方に対して単一のインスタンス化を実行し、その場でオブジェクトを構築するmake_shared
テンプレートを使用することにより、余分な割り当てを回避できます。
残りの追加コストは、カウントのインクリメントとデクリメント(両方のアトミック操作)や削除のテストなど、mallocの呼び出しを2倍にするのに比べて非常に小さくなります。ポインター/共有ポインターをどのように使用しているかでコードを提供できる場合、コードで実際に何が行われているかについてより良い洞察を得ることができます。
「リリース」モードで試して、より近いベンチマークを取得するかどうかを確認してください。デバッグモードでは、STLで多くのアサーションがオンになり、多くの処理が遅くなります。
shared_ptr
are生のポインタよりも著しく遅い。そのため、実際に必要共有所有権セマンティクスを使用する場合にのみ使用する必要があります。
それ以外の場合は、他のいくつかの種類のスマートポインターを使用できます。 scoped_ptr
およびauto_ptr
(C++ 03)またはunique_ptr
(C++ 0x)には両方の用途があります。多くの場合、最良の解決策は、いかなる種類のポインターも使用せず、代わりに独自のRAIIクラスを作成することです。
shared_ptr
は、参照カウンタをインクリメント/デクリメント/読み取りする必要があります。実装とインスタンス化方法によっては、refカウンタが個別に割り当てられ、キャッシュミスが発生する可能性があります。また、refカウンターにアトミックにアクセスする必要があるため、オーバーヘッドが追加されます。
これ以上のデータなしでこれに答えることは不可能です。 shared_ptrバージョンのスローダウンの原因を正確に特定するために、コードのプロファイルを作成しましたか?コンテナを使用すると確かにオーバーヘッドが増加しますが、10倍遅くなると驚くでしょう。
VSTSには、これを30秒程度実行できる場合にCPU使用率を正確に特定するニースperfツールがあります。 VS Performance Toolsまたは他のプロファイリングツールセットにアクセスできない場合は、デバッガーでshared_ptrコードを実行し、10回または15回侵入して、すべての時間を費やしている場所のブルートフォースサンプルを取得します。これは驚くほど直感に反して効果的であることがわかりました。
[編集]コードのそのバリアントでは、値によってshared_ptrを渡さないでください。constにrefを使用してください。この関数が頻繁に呼び出されると、測定可能な-veの影響があります。
参照のinc/dec操作にアトミック命令を使用するため低速であり、ひどい低速です。 C++でGCが本当に必要な場合は、単純なRF GCを使用して、さらに開発されたRC戦略またはGCのトレースを使用しないでください。 http://www.hboehm.info/ gc / は、重要なタスクを高速化しない場合に適しています(ただし、「スマートポインター」ナイーブRCよりもはるかに優れています)。