web-dev-qa-db-ja.com

次のクラスタリング/結合アルゴリズムを最適化/並列化する方法:

私のアルゴリズムは比較的小さく、科学コードの合計実行時間の約60%(3600の57行)を占めるので、実行していることを最適化してコードの順序を決める方法を見つけたいと思います- cilk_for並列構造を適用できるように独立しています。

これが口頭で行う処理Segmentstd::vector)というカスタムオブジェクトへのポインタのvector<Segment*> newSegmentがあります。各Segmentには、整数(メッシュインデックス)のstd::vectorが含まれます。この関数では、anySegmentanyとオーバーラップし、オーバーラップが定義されているものを見つけたいと思います。数直線上で重複するメンバーindicesとして。それらが重複している場合は、それらを結合し(A.indicesB.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;
}

アイデア

  1. 私はSegmentオブジェクトの順序を気にしないので、それらは順序のないコンテナにある可能性がありますか?
  2. 私のアルゴリズムでは、各セグメントの最初と最後のindicesを調べることで、重複を見つけます。 indicesをフェッチするときにstd::is_sorted(そしておそらくstd::sort)を実行します。これは、インデックスがさらに挿入されるとリストが変更される可能性があるためです。たぶん、indicesstd::setではなくstd::vectorに入れて、明示的な並べ替えチェック/並べ替えを保存できますか?
  3. indicesを編集しながら編集することで、順序に依存するようになると確信しています。おそらく、無向グラフの概念を使用してコードを次の構成に分割し、順序に依存しないようにすることができます。

    • エッジ検出(indicesを変更せずに)
    • グラフ走査を使用して、接続されたノードのクラスター(重複するSegmentオブジェクト)を結合する
    • 空のSegmentオブジェクトを削除します

質問

  1. 上記のアイデアのいずれかは、価値があるか、パフォーマンスに無視できますか?
  2. 他にどのようにそれを最適化できますか?
  3. (上記でない場合)アルゴリズムを順序に依存しないようにするにはどうすればよいですか?
5
Stershic

is_sorted()関数はおそらく高価なので、避ける必要があります。ループに入る前に、最初にすべてを一度にソートしてみませんか?

コードを最適化する最良の方法は、Nのネストされたループを回避する新しいアルゴリズムを発明することです。これは、O(N ^ 2)の複雑さがあるためです(「big-Oh表記」を参照してください)。以下のBart van Ingen Schenauのコメントを参照してください。これを達成する方法について。

4
Mike Nakis

私は@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が このコメント で言っているように、マージはかなりメモリにバインドされているため、並列化がうまくいかない可能性があります

0
Xavier Combelle