木の並列/ GPU処理に関するいくつかの論文を見てきましたが、それらをざっと見ただけで、それらが何をしているかを理解することができませんでした。この図の Parallelization:Binary Tree Traversal に、最もわかりやすい説明がありました。
しかし、論文をフォローするのは難しい。
ツリーの並列処理のアルゴリズムの概要を説明できるかどうか疑問に思っています。どういうわけかこれが可能であると想像でき、それに関する論文を見るとそれが可能であることが示唆されますが、それを実現するためにあなたが何をするのか本当に考えることはできません。
それが何らかの助けになるなら、具体的には、一致を見つけるために B + tree をトラバースする方法を疑問に思っています。
更新
ここに別の図があります( here から)。これは、いくつかの光を放つようですが、理解が困難です。
veryシンプルです。
左の子、次に右の子を再帰する場合は、代わりに、左に再帰するタスクを開始し、右に続きます。右で終わったら、左で待たなければならないかもしれませんが、長くはないバランスの取れたツリーで
ツリーの深さN
にいる場合、2^N
コアが機能する可能性があります
その特定の図は、おそらくそれがタスクをキューイングしていないため、特定の深さでタスクをスポーンするだけです。これは、ツリーの構造の知識を使用して、スケジューラに過負荷をかけないようにします
タスクキューとスレッドプールがある場合、実装はそれらを活用して、生成されるタスクの量を心配する必要がありません。
あなたが持っていると言う
class Node
{
int data; // or w/e
Node * left;
Node * right;
template <typname Action>
void SerialTraverse(Action action)
{
action(data); // Pre-order traversal
if (left) left->SerialTraverse(action);
// Traverse the left
if (right) right->SerialTraverse(action);
// Traverse the right
}
}
namespace library
{
template<typename Task, typename Class, typename Arg>
std::future<void> enqueue_task(Task task, Class * obj, Arg arg);
// on some other thread, call "obj->task(arg);"
// returns a handle upon which we can wait
}
次に、SerialTraverse
を
template <typname Action>
void ParallelTraverse(Action action)
{
action(data);
std::future<void> fut;
if (left) fut = library::enqueue_task(ParallelTraverse, left, action);
// start traversing left on another thread
if (right) right->ParallelTraverse(action);
// traverse right on this thread, in parallel to traversing left
if (fut.valid()) fut.wait();
// wait for the left traversal to end
}
ツリーのレベルごとにgpu呼び出しを行う方が良いと考えることができますが、すべてを1つの呼び出しにまとめ、反復のステップを実行し、同時の全体の量によってコアを操作に戻すと、より簡単になると思いますアクセス。
ここでは、GPUのツリーでキーストアを実行するための設定を示します。
32ビットuintテクスチャを使用し、次の形式を使用します-> [NEXTFREEPOSITION] [NODE MATCHING STRING] [PARALLEL POINTER] [SERIAL POINTER] [NODE MATCHING STRING] [PARALLEL POINTER] [SERIAL POINTER] [NODE MATCHING STRING] [PARALLEL POINTER] [シリアルポインター] [ノードマッチングストリング] [並列ポインター] [シリアルポインター] [ノードマッチングストリング] [並列ポインター] [シリアルポインター] [ノードマッチングストリング] [並列ポインター] [シリアルポインター] ...次の空き位置ポイントここで最後に別のIOPを追加します。
パラレルポインターは、自分の足の横の脚を指し、シリアルポインターは、ツリーのさらに深いところを指します。並列ノードがnullの場合は、最後に到達したことを意味し、一致するものがないため中止されます。あなたが最後のレベルにいるとき、あなたは葉の内容を入れて、正常に完了します。
テクスチャーをランダムに移動することでgpuをかなり傷つけますが、空間的に並列に分割されているため、ログシリーズレベルを使用できるため、あまり多くを行う必要はありません。
直接計算で行われる読み取りコードの例を次に示します。
//本当に短いだけです。
[numthreads(32, 32, 1)] //put your 200k parallel accesses here. :)
void cs_zc_read_read(uint3 DTid : SV_DispatchThreadID )
{
//get the first link pointer with a kickstart array, cause the start reuses
//too much and it makes too many parallel nodes.
uint link=buffer1[buffer0[(DTid.x+DTid.y*RES)*ZCKEY32+0].f].f;
uint i;
while(i<ZCKEY32;i++)
{
uint match32=buffer0[(DTid.x+DTid.y*RES)*ZCKEY32+i].f
uint match232;
uint lnk[2];
match232=buffer2[link];
lnk[0]=buffer2[link+2];
lnk[1]=buffer2[link+1];
if(match32==match232)
{
link=lnk[0]; //this steps in series.
i++;
if(i==ZCKEY32)
{
//success, its an exact match.
link=lnk[0]; //get the routing position
//get two routings.
BufferOut[(DTid.x+DTid.y*RES)*2+0].f=buffer2[link+1].f;
BufferOut[(DTid.x+DTid.y*RES)*2+1].f=buffer2[link+2].f;
}
}
else
{
link=lnk[1]; //this steps in parallel
if(link==0){i=100000;} //abort, its a no match
}
}
}
面倒なので、書き込みの並列化について心配する必要はありません。 = pしかし、もっとお尻を蹴りたい場合は、3値コードを使用して複数の値を収集し、柔軟性を高めますが、並行して収集する量でコストを乗算する必要があります。なぜなら、「コアを飽和させたら、並列スレッドを増やしても速度が向上しないからです」。