私のアルゴリズムは比較的小さく、科学コードの合計実行時間の約60%(3600の57行)を占めるので、実行していることを最適化してコードの順序を決める方法を見つけたいと思います- cilk_for
並列構造を適用できるように独立しています。
これが口頭で行う処理:Segment
(std::vector
)というカスタムオブジェクトへのポインタのvector<Segment*> newSegment
があります。各Segment
には、整数(メッシュインデックス)のstd::vector
が含まれます。この関数では、anySegment
がanyとオーバーラップし、オーバーラップが定義されているものを見つけたいと思います。数直線上で重複するメンバーindices
として。それらが重複している場合は、それらを結合し(A.indices
をB.indices
に挿入)、1つを削除します(A
を削除します)。
例1:A.indices
= {1,2,3} B.indices
= {4,5,6}は重複しません。 何もしない
例2:A.indices
= {1,2,4} B.indices
= {3,5,6}は重複しています。 A
=削除済みB.indices
= {1,2,3,4,5,6}
オーバーラップはまばらですが、存在します。
これが現在のコードです:
主なアルゴリズム:
//make sure segments don't overlap
for (unsigned i = 0; i < newSegment.size(); ++i) {
if (newSegment[i]->size() == 0) continue;
for (unsigned j = i + 1; j < newSegment.size(); ++j) {
if (newSegment[i]->size() == 0) continue;
if (newSegment[j]->size() == 0) continue;
int i1 = newSegment[i]->begin();
int i2 = static_cast<int>(newSegment[i]->end());
int j1 = newSegment[j]->begin();
int j2 = static_cast<int>(newSegment[j]->end());
int L1 = abs(i1 - i2);
int L2 = abs(j1 - j2);
int dist = max(i1,i2,j1,j2) - min(i1,i2,j1,j2);
//if overlap, fold segments together
//copy indices from shorter segment to taller segment
if (dist <= L1 + L2) {
unsigned more, less;
if (newSegment[i]->slope == newSegment[j]->slope) {
if (value_max[i] > value_max[j]) {
more = i;
less = j;
} else {
more = j;
less = i;
}
} else if (newSegment[i]->size() == 1) {
more = j; less = i;
} else if (newSegment[j]->size() == 1) {
more = i; less = j;
} else assert(1 == 0);
while(!newSegment[less]->indices.empty()) {
unsigned index = newSegment[less]->indices.back();
newSegment[less]->indices.pop_back();
newSegment[more]->indices.Push_back(index);
}
}
}
}//end overlap check
//delete empty segments
vector<unsigned> delList;
for (unsigned i = 0; i < newSegment.size(); ++i) {
if (newSegment[i]->size() == 0) { //delete empty
delList.Push_back(i);
continue;
}
}
while (delList.size() > 0) {
unsigned index = delList.back();
delete newSegment.at(index);
newSegment.erase(newSegment.begin() + index);
delList.pop_back();
}
関連するSegment
オブジェクトクラスの定義とメンバー関数:
class Segment{
public:
Segment();
~Segment();
unsigned size();
int begin();
unsigned end();
std::vector<int> indices;
double slope;
};
int Segment::begin() {
if (!is_sorted(indices.begin(),indices.end())) std::sort(indices.begin(),indices.end());
if (indices.size() == 0) return -1;
return indices[0];
}
unsigned Segment::end() {
if (!is_sorted(indices.begin(),indices.end())) std::sort(indices.begin(),indices.end());
return indices.back();
}
unsigned Segment::size() {
unsigned indSize = indices.size();
if (indSize == 1) {
if (indices[0] == -1) return 0;
}
return indSize;
}
アイデア:
Segment
オブジェクトの順序を気にしないので、それらは順序のないコンテナにある可能性がありますか?indices
を調べることで、重複を見つけます。 indices
をフェッチするときにstd::is_sorted
(そしておそらくstd::sort
)を実行します。これは、インデックスがさらに挿入されるとリストが変更される可能性があるためです。たぶん、indices
をstd::set
ではなくstd::vector
に入れて、明示的な並べ替えチェック/並べ替えを保存できますか?indices
を編集しながら編集することで、順序に依存するようになると確信しています。おそらく、無向グラフの概念を使用してコードを次の構成に分割し、順序に依存しないようにすることができます。
indices
を変更せずに)Segment
オブジェクト)を結合するSegment
オブジェクトを削除します質問
is_sorted()
関数はおそらく高価なので、避ける必要があります。ループに入る前に、最初にすべてを一度にソートしてみませんか?
コードを最適化する最良の方法は、Nのネストされたループを回避する新しいアルゴリズムを発明することです。これは、O(N ^ 2)の複雑さがあるためです(「big-Oh表記」を参照してください)。以下のBart van Ingen Schenauのコメントを参照してください。これを達成する方法について。
私は@BartVanIngenSchenauと同じアルゴリズムに到達しました このコメント 基本的に各セグメントの最小要素に基づいてセグメントのセットをソートします。次に、2つの隣接する要素がオーバーラップするのは、_Segment[i].max >= Segment[i+1].min
_の場合のみです。
しかし、並べ替えはまったく不要に見え、max要素とmin要素のみを保持するように追加したいと思います。セグメントをマージするときにそれらを更新するだけです。 _(segment1+segment2).min = min(segment1.min,segment2.min)
_および_(segment1+segment2).max = max(segment1.max,segment2.max)
_さらに、セグメントが最小要素でソートされている場合、_(Segment[i]+Segment[i+1]).min = segment[i].min
_があります(ただし、この最後のものは時期尚早の最適化である可能性があります)。_+
_ 2つのセグメントのマージ。
キャッシュの局所性の場合、マージに最適なのは、次のレイアウトに類似したレイアウトを使用することです。
_ptr_to_2nd_segment
n_elt_of_1st_segment,
min_elt_of_1st_segment,
[
[other_elts_of_1st_segment,]
max_elt_of_1st_segment,]
ptr_to_3rd_segment
n_elt_of_2nd_segment,
min_elt_of_2nd_segment,
[
[other_elts_of_2nd_segment,]
max_elt_of_2nd_segment,]
...
_
この構成で2つの要素をマージするのは非常に簡単です。必要に応じて、ptrを次の要素に更新し、要素の数を追加し、2番目のセグメント要素をシフトし、最大要素を交換するだけです。これにより、マージのたびにジャンクが発生します(32ビットアーキテクチャでは8バイト、64ビットアーキテクチャでは16バイト)。このようなジャンクをサポートできるかどうかは、アプリケーションによって異なります(さらに、アルゴリズムの2回の反復の間に一種のガベージコレクションを実行できます)。
並列化の場合、セグメントのセットがmin要素でソートされると、セグメントのセットをn個の部分に分割し、個別にマージを実行できます。次に、各パーツの境界でのみマージします。しかし、@ MikeNakisが このコメント で言っているように、マージはかなりメモリにバインドされているため、並列化がうまくいかない可能性があります