5

超高速である必要があるプログラムを作成しています。CUDA を使用して GPU でいくつかのことを実行してから、CPU でいくつかの計算を行います。このためには、高度に最適化された GPU データ構造を、CPU で簡単に使用できるものに変換する必要があります。私のデータは基本的に、グリッドに配置されたグラフです。現在、CPU 部分に std::vector を使用しています。多くの s を実行するとかなりのオーバーヘッドが発生することがわかっておりpush_back()、少なくともグラフ内にいくつの頂点があるかはわかっているので、次のコードを使用します。

new_graph.resize(blockSize * blockSize);
for (unsigned long long y = 0; y < blockSize; y++) {
    for (unsigned long long x = 0; x < blockSize; x++) {
        int idx = y * blockSize + x;
        new_graph[idx] = Vertex(x, y);
    }
}

その後、エッジを追加します。残念ながら、頂点ごとにいくつのエッジがあるかはわかりませんが、それが 8 を超えることはないことはわかっています。したがってreserve()、エッジに使用する各 std::vector で 8 を使用します。

ただし、これはどちらも非常に遅いようです。グラフ自体に通常の配列を使用すると (つまり、基本的に外側の std::vector を置き換える)、その部分の速度は大幅に向上します (10 倍程度)。

グラフの場合、これは実行可能ですが、エッジの場合は実際にはそうではありません。これらのエッジで後処理を行うためです。このためには、std::vector のような動的なものが本当に必要です (いくつかのエッジを追加します)。

現在、データを std::vector に変換すると、GPU でアルゴリズムを実行するよりも 10 倍遅くなります (これはスマート MST アルゴリズムです)。オーバーヘッドが大きすぎるため、これは私が望んでいるものではありません。

誰かが何が起こっているのか、またはこれを修正する方法を知っていますか?

ps -O2 を指定してコンパイルします。これにより、大きな違いが生じる可能性があることが既にわかっているからです。-O3 でも試してみましたが、実際の違いはありません。

頂点は次のように定義されます。

struct Pos {
    int x, y;
    Pos() {
        x = 0;
        y = 0;
    }

    Pos(int x, int y) {
        this->x = x;
        this->y = y;
    }
};

struct Vertex {
    Pos pos;
    bool hidden;
    unsigned long long newIdx;
    Vertex() {
        this->pos = Pos();
        this->hidden = false;
        this->numEdges = 0;
        this->numRemovedEdges = 0;
    }

    Vertex(Pos &pos) {
        this->pos = pos;
        this->hidden = false;
        this->numEdges = 0;
        this->numRemovedEdges = 0;
    }

    Vertex(int x, int y) {
        this->pos = Pos(x, y);
        this->hidden = false;
        this->numEdges = 0;
        this->numRemovedEdges = 0;
    }
    int numEdges;
    int numRemovedEdges;
    std::vector<Edge> edges;
    std::vector<bool> removed;
    std::vector<bool> doNotWrite;
};
4

4 に答える 4

3

vectorおそらく、その要素用のスペースを予約する動的メモリ割り当てにお金を払っているのでしょうか?

最適な場合でも、それぞれreserveに少なくとも3 つのメモリ割り当てがありますVertex( に 1 つ、edgesに 1 つ、 に 1removeddoNotWrite)。動的メモリ割り当ては、ここで実行しようとしている高性能のものと比較して、潜在的に高価です。

十分な大きさが保証されている単純な古い配列を使用するか (スペースを浪費する可能性があります)、vector特定のニーズに合わせて特別なメモリ アロケータを と共に使用します。


また、メモリ順に要素にアクセスしますか? あなたの例はそう示唆しているようですが、すべての場合にそうしますか?


また、必要Vertex.posですか?グリッド内の の位置から推測できませんか?Vertex

于 2012-04-04T17:21:55.170 に答える
1

同様の状況で最近使用した別のソリューションがあります。llvm パッケージには SmallVector クラスがあります。std::vector と非常によく似たインターフェイスを提供しますが、固定数の要素をインラインに保つことができます (そのため、ベクトルがその初期制限を超えない限り、追加のメモリ割り当ては発生しません)。SmallVector がその初期サイズを超えて拡大しようとすると、メモリ ブロックが割り当てられ、すべてのアイテムがそこに移動されます。すべてが 1 つの透過的なステップで行われます。

この SmallVector で修正しなければならなかった点はいくつかあります。

  1. インプレースに配置できるアイテムの最小数は 2 であるため、たとえば 99.99% のケースで 1 つのアイテムが使用されると、かなりのオーバーヘッドが発生します。
  2. swap() を通常使用してメモリを解放する ( SmallVector().swap(vec) ) ではメモリが解放されないため、自分で実装する必要がありました。

SmallVector クラスのソース コードについては、llvm の最新バージョンを参照してください。

于 2012-04-04T20:39:46.990 に答える
1

CPU データ構造は、動的メモリ割り当ての数、不要な割り当て操作、および各頂点の全体的なサイズのために、非常に非効率的です。この構造の最適化を検討する前に、CPU データ構造と GPU データ構造の間のデータ フローを理解しておくとよいでしょう。2 つのフォーマット間の変換には多くの時間がかかる可能性が高いからです。これは、GPU 構造が CPU 側で使用されないのはなぜでしょうか?

これを CPU 側からのみ見ていて、AoS データ構造を維持したい場合は、1. Vertex データ構造を単純化します。2. すべての動的メモリ割り当てを削除します。各 std::vector は dynb 3 を実行します。removed と doNotWrite を std::bitset<8> に置き換えます。4. numRemoveEdges を削除します。これは、removed.count() です。5. Edge が小さい場合は、Edge エッジを宣言する方が速い場合があります[8]。6. ベクトルを使用する場合は、プール アロケータの使用を検討してください。7. Vertex のデータ要素をサイズ順に並べ替えて、Vertex のサイズを縮小します。

これらの推奨事項はすべて、GPU とデータを共有するための最適なソリューションではない可能性が非常に高くなります。プール アロケーターを使用し、UVA (CUDA Linux) を使用する場合は、単一のメモリ コピーでデータを GPU に簡単にコピーできます。

于 2012-04-05T03:17:51.657 に答える
0

1つの頂点オブジェクトを作成してx値とy値をそのオブジェクトにmemcpyし(ループごとにコンストラクターを呼び出す必要がないように)、頂点全体をstd :: vectorにmemcpyすることはできませんか?ベクトルのメモリは通常の配列のように配置されることが保証されているため、すべての抽象化をバイパスしてメモリを直接操作できます。複雑なものは必要ありません。また、GPUから取得したデータを、ブロック全体を一度にmemcpyできるようにレイアウトして、さらに節約できるかもしれません。

于 2012-04-04T16:09:31.877 に答える