0

大量のヒープメモリが必要な関数を書いています。forパフォーマンスを向上させるために(コンパイルオプションなどを介して)、これらのデータが特定のループ内で頻繁にアクセスされることをコンパイラーに通知することは可能ですか?

スタックを使用できない理由は、格納する必要のある要素の数が多く、それを実行しようとするとセグメンテーション違反が発生するためです。

現在、コードは機能していますが、もっと速くなる可能性があると思います。

更新:私はこのようなことをしています

vector< set<uint> > vec(node_vec.size());
for(uint i = 0; i < node_vec.size(); i++)
  for(uint j = i+1; j < node_vec.size(); j++)
    // some computation, basic math, store the result in variable x
      if( x > threshold ) {
         vec[i].insert(j);
         vec[j].insert(i);
      }

いくつかの詳細:
-hash_setを使用しましたが、シミュレーションの目的で使用しているすべてのマシンでhash_setが使用できるわけではないという事実に加えて、少し改善しました
-配列を使用してスタックにvecを割り当てようとしましたが、前述のように、次の場合にセグメンテーション違反が発生する可能性があります要素の数が多すぎます

node_vec.size()がたとえばkと等しい場合、kは数千のオーダーであり、vecはnode_vecの4倍または5倍大きいと予想されます。この桁数では、コードを何度も実行する必要があることを考えると、コードは遅いように見えます。もちろん、私はこれらの呼び出しを並列化するためにマルチスレッドを使用していますが、関数自体を現在表示されているものよりもはるかに高速に実行することはできません。

たとえば、高速データ検索などのために、vecをキャッシュメモリに割り当てることは可能でしょうか?

4

5 に答える 5

3

大量のヒープメモリが必要な関数を書いています...特定のforループ内で頻繁にアクセスされます

これは、コンパイラレベルで実際に最適化できるものではありません。あなたの懸念は、「古くなった」(ページアウトされた)可能性のあるメモリがたくさんあることだと思いますが、特定の時点で、すべてを何度も繰り返す必要があり、メモリが必要ありませんディスクにページアウトされるページ。

パフォーマンスを向上させるには、プラットフォーム固有の戦略を調査する必要があります。ページをメモリに保持することは、mlockallまたはを使用して実現できますVirtualLockが、実際にはこれを行う必要はありません。ただし、アプリケーションのメモリページをRAMにロックすることの意味を知っていることを確認してください。他のプロセスからメモリを占有しています。

また、断片化の少ないヒープ(ただし、この問題にはまったく関係がない場合もあります)と、ループに関するキャッシュラインについて説明しているこのページを調査することもできます。for

後者のページは、メモリアクセスに関してCPUがどのように機能するか(通常は気にする必要のない詳細)の要点について説明しています。

例1:メモリアクセスとパフォーマンスループ1と比較して、ループ2の実行速度はどれくらい速いと思いますか。

int[] arr = new int[64 * 1024 * 1024];

// Loop 1
for (int i = 0; i < arr.Length; i++) arr[i] *= 3;

// Loop 2
for (int i = 0; i < arr.Length; i += 16) arr[i] *= 3;

最初のループは配列内のすべての値を3で乗算し、2番目のループは16番目ごとにのみ乗算します。2番目のループは最初のループの作業の約6%しか実行しませんが、最近のマシンでは、2つのforループにほぼ同じ時間がかかります。私のマシンではそれぞれ80ミリ秒と78ミリ秒です。

于 2011-04-16T02:16:09.613 に答える
1

アップデート

vector< set<uint> > vec(node_vec.size());
for(uint i = 0; i < node_vec.size(); i++)
  for(uint j = i+1; j < node_vec.size(); j++)
    // some computation, basic math, store the result in variable x
      if( x > threshold ) {
         vec[i].insert(j);
         vec[j].insert(i);
      }

条件が真になる頻度がわからないため、それでもあまり表示されませんx > thresholdx > thresholdが非常に頻繁に当てはまる場合は、挿入するstd::setたびに動的メモリ割り当てを実行する必要があるため、ボトルネックになる可能性がありuintます。

また、「何らかの計算」が実際に何を意味するのか/実行するのか/であるのかはわかりません。それが多くのことをする場合、またはそれがボトルネックになる可能性がある間違った方法で行う場合。

また、結果にアクセスする方法がわかりません。

とにかく、勘で:

    vector<pair<int, int> > vec1;
    vector<pair<int, int> > vec2;

    for (uint i = 0; i < node_vec.size(); i++)
    {
        for (uint j = i+1; j < node_vec.size(); j++)
        {
            // some computation, basic math, store the result in variable x
            if (x > threshold)
            {
                vec1.push_back(make_pair(i, j));
                vec2.push_back(make_pair(j, i));
            }
        }
    }

その形式で結果を使用できる場合は、これで完了です。それ以外の場合は、後処理を行うことができます。std::setもう一度(明らかに)コピーしないでください。に固執してみてくださいstd::vector<POD>。たとえば、次のようにベクトルにインデックスを作成できます。

    // ...
    vector<int> index1 = build_index(node_vec.size(), vec1);
    vector<int> index2 = build_index(node_vec.size(), vec2);
    // ...
}    

vector<int> build_index(size_t count, vector<pair<int, int> > const& vec)
{
    vector<int> index(count, -1);

    size_t i = vec.size();
    do
    {
        i--;
        assert(vec[i].first >= 0);
        assert(vec[i].first < count);
        index[vec[i].first] = i;
    }
    while (i != 0);

    return index;
}

ps.:あなたのループはメモリに縛られていないとほぼ確信しています。確かではありません...あなたが私たちに見せていない「ノード」が本当に大きい場合、それはまだ大きいかもしれません。


元の答え:

簡単なI_will_access_this_frequently_so_make_it_fast(void* ptr, size_t len)解決策はありません。

しかし、あなたはいくつかのことをすることができます。

  1. コンパイラがクリティカルループ内で呼び出されるすべての関数の実装を「認識」できることを確認してください。コンパイラーが実装を「見る」ことができるために必要なものは、コンパイラーによって異なります。ただし、確実にする方法が1つあります。ループの前に、関連するすべての関数を同じ変換単位で定義し、それらをとして宣言しますinline

    これは、これらの重要なループで「外部」関数を呼び出さないようにする必要があることも意味します。また、「外部」関数とは、システムコール、ランタイムライブラリ、DLL/SOに実装されたものなどを意味します。また、仮想関数を呼び出さないでください。また、関数ポインターを使用しないでください。そしてもちろん、(クリティカルループ内で)メモリを割り当てたり解放したりしないでください。

  2. 最適なアルゴリズムを使用していることを確認してください。アルゴリズムの複雑さが必要以上に高い場合、線形最適化は重要ではありません。

  3. 可能な限り最小のタイプを使用してください。たとえば、仕事をするint場合は使用しないでください。signed charこれは通常はお勧めしませんが、大量のメモリを処理する場合は、パフォーマンスが大幅に向上する可能性があります。特に非常にタイトなループで。

  4. メモリをコピーまたはいっぱいにするだけの場合は、またはを使用しmemcpyますmemset。チャンクが約50〜100バイトより大きい場合は、これら2つの関数の組み込みバージョンを無効にします。

  5. キャッシュに適した方法でデータにアクセスするようにしてください。最適なのは「ストリーミング」です。つまり、昇順または降順のアドレスでメモリにアクセスします。一度に数バイト先に「ジャンプ」することはできますが、ジャンプしすぎないでください。最悪の事態は、メモリの大きなブロックへのランダムアクセスです。たとえば、p[0]からp[1]が「右へ」(x + 1)のステップである2次元マトリックス(ビットマップイメージなど)で作業する必要がある場合は、内側のループがxと外側の増分y。逆にすると、パフォーマンスが大幅に低下します。

  6. ポインターにエイリアスがない場合は、コンパイラーに指示できます(これがどのように行われるかはコンパイラーによって異なります)。エイリアスフリーの意味がわからない場合は、説明が範囲を超えているため、ネットとコンパイラのドキュメントを検索することをお勧めします。

  7. 必要に応じて、組み込みのSIMD命令を使用します。

  8. 近い将来必要になるメモリ位置がわかっている場合は、明示的なプリフェッチ命令を使用してください。

于 2011-04-16T03:06:15.103 に答える
0

コンパイラオプションではそれを行うことはできません。使用法(挿入、ランダムアクセス、削除、並べ替えなど)によっては、より適切なコンテナーを取得できる場合があります。

于 2011-04-16T02:07:39.857 に答える
0

コンパイラーは、データがループ内で頻繁にアクセスされていることをすでに認識しています。

于 2011-04-16T02:11:10.517 に答える
0

ループを実行する前にヒープからデータを1回だけ割り当てると仮定すると、@ lvellaのように、メモリはメモリであり、頻繁にアクセスされる場合は、実行中に効果的にキャッシュされる必要があることに注意してください。

于 2011-04-16T02:16:11.783 に答える