通常/並列プログラミングの新機能のアイデア(C ++拡張)

こんにちは親愛なる読者。



再入門計画(PPPV / FPPV)を伴うプロシージャ/関数に基づいたC ++拡張機能での古典的および並列プログラミングの基本的なアイデアを議論することに興味のあるすべての人を招待します。 少なくとも、静的または動的な実行プランを持つプロシージャまたは関数です。 プランは、一般的に、構造要素で構成されるデッキであり、各構造要素には、対応する関数/プロシージャのパラメータとタイプと名前が類似したフィールドがあります。 計画は、外部から(いくつかの簡単なトリックを使用してプロシージャ/関数を呼び出す前に)、および内部から(これが主なアプローチです)計画のplan_firstの開始(...)またはplan_lastの終了(...)



PPPV / FPPVは、計画に従って順次または並行して実行されます。 シーケンシャルモードでは、計画の各要素に対して再度呼び出され(要素はデフォルトで計画の最初から抽出されます)、完全に(PPPV / FPPVが自然に完了するまで、または戻るまで)実行されます。 呼び出しごとに、PPPV / FPPVパラメーターには、最新計画要素の同じフィールドからのデータが入力されます。 すでに述べたように、いずれかの段階の実行プロセスで、新しい段階を計画に挿入することができます。これは、PPPV / FPPVによってさらに実行されます。 並列モード (最も単純な場合)をサポートするために、手順に加えて、特別なマーカーバリアをプランに挿入して、プランをグループに分割できます。 plan_group_parallelizeディレクティブを使用すると、現在の(プランの先頭にある)グループの並列実行を有効にできますが、タスクプール(タスクプール)と見なされ、そこからプロセッサ/コアがタスクステージを収集します。 plan_group_vectorizeディレクティブを使用すると、タスクのグループをマルチコアビデオカードなどのベクターコンピューターに送信できます(ただし、一部の機能はプログラムデザインに表示される場合があります。たとえば、どのプログラムブロックがベクターコンピューター専用で、 CPU、これ-CPUとベクターの両方

デバイス)。



すでにそのような基本的なアプローチは少なくとも以下を提供します:





理解を複雑にしないために、すぐにいくつかの例を挙げます。



ツリー内の要素の並列合計。 削減パラメーターSumResultが使用されます。



reenterable[ARR_SIZE] TreeSum(TreeNode * Cur, reduction(+) DataItem & SumResult) { if (Cur==Root) plan_group_parallelize; if (Cur->Left) plan_last(Cur->Left,SumResult); if (Cur->Right) plan_last(Cur->Right,SumResult); SumResult = Cur->Data; }
      
      





リーウェーブアルゴリズム:迷路でパスを見つける。



 const int NL = 10; /*   */ const unsigned char W = 0xFF; /*  */ /*  */ unsigned char Labirint[NL][NL] = { {W,W,W,W,W,W,W,W,W,W}, {W,0,0,0,0,0,0,0,0,W}, {W,0,W,W,0,0,W,0,0,W}, {W,0,0,W,0,0,W,0,0,W}, {W,0,0,W,W,0,W,0,0,W}, {W,0,0,0,W,W,W,0,0,W}, {W,0,0,0,0,0,0,0,0,W}, {W,0,0,0,0,0,0,0,0,W}, {W,0,0,0,0,0,0,0,0,W}, {W,W,W,W,W,W,W,W,W,W}, }; /*       , , ,  */ signed char OffsX[4] = {-1,0,0,+1}; signed char OffsY[4] = {0,-1,+1,0}; const char FirstX = 8; /*   */ const char FirstY = 8; const char LastX = 5; /*   */ const char LastY = 4; chain[NL*NL] FindLi(unsigned char X, unsigned char Y, int Num) throw(unsigned char X, unsigned char Y) { char Found = 0; for (int i=0; !Found && i<4; i++) { /*    */ unsigned char X1 = X+OffsX[i]; unsigned char Y1 = Y+OffsY[i]; if (X1>=0 && X1<NL && Y1>=0 && Y1<NL && Labirint[Y1][X1]==0) { /*      */ Labirint[Y1][X1] = Num; /*  */ if (X1==LastX && Y1==LastY) /*   */ Found = 1; /*   */ else /*    */ plan_last(X1,Y1,Num+1); /*    */ } } if (Found) { clear_plan; /*     */ X = LastX; Y = LastY; throw_last(X,Y); /*   ""   () */ while (X!=FirstX || Y!=FirstY) { /*       */ char PointFound = 0; /*     */ for (int i=0; !PointFound && i<4; i++) { unsigned char X1 = X+OffsX[i]; unsigned char Y1 = Y+OffsY[i]; /*      */ if (X1>=0 && X1<NL && Y1>=0 && Y1<NL && Labirint[Y1][X1] && Labirint[Y1][X1]<Labirint[Y][X]) { /*         */ /*      ,     */ PointFound = 1; throw_first(X1,Y1); /*    ()   */ X = X1; Y = Y1; /*         */ } } } } else if (plan_empty) cout<<"NO PATH\n"; /*      */ } chain[NL*2] OutLi(unsigned char X, unsigned char Y) { cout<<"("<<(int)Y<<","<<(int)X<<") "; } int main() { cout<<"Find the path in the simple labirint (Li algorithm) :\n"; Labirint[FirstY][FirstX] = 1; plan_chain(0,FindLi(FirstX,FirstY,2),OutLi(0,0)); /*     */ cout<<"\n"; return 0; }
      
      





また、 マルチコアビデオカードを使用した別の抽象的な例についても説明しません。 誰かがそれがどのように機能するかを推測することに興味があるかもしれません。



 #pragma plan vectorized #include <iostream> using namespace std; #pragma plan common begin #define N 5 #define threads 100 #pragma plan common end #pragma plan gpu begin #define addition 0.01 #pragma plan gpu end float MUL = 3.14; float * _OUT = NULL; reenterable void proc(bool init, int k, _global(1) float * mul, _global(threads) int * sizes, int n, _local(__planned__.n) float * out) { int i; if (init) { for (i = 0; i < threads; i++) { plan_last(false, i, mul, sizes, sizes[i], out); out += sizes[i]; } plan_group_vectorize(NULL); } else for (i = 0; i < n; i++) { *out = k*(*mul); #ifdef __GPU__ *out += addition; #endif out++; } } int main() { int * sizes = new int[threads]; int NN = 0; for (int i = 0; i < threads; i++) { sizes[i] = 1 + i % 2; NN += sizes[i]; } _OUT = new float[NN]; cout<<"MAX group size = "<<vector_max_size(NULL)<<endl; proc(true, 0, &MUL, sizes, 0, _OUT); for (int i = 0; i < NN; i++) cout<<_OUT[i]<<" "; cout<<endl; delete[] _OUT; delete[] sizes; return 0; }
      
      





ここで、PPPV / FPPVを計算トポロジ(グラフ)の基本単位と見なし、あるPPPV / FPPVが別の(グラフに隣接する)PPPV / FPPVの計画を補充できる構造を決定する場合、かなり複雑な計算トポロジで実際に作業できることに注意してください。さらに、共有メモリと個別メモリの両方の場合(たとえば、クラスタ上-グラフによる計画要素の転送は、単純なネットワーク転送操作を使用して実行されます)。 別のPPPV / FPPVの計画補充操作は、 throw_first(...)およびthrow_last(...)と呼ばれます 。 それらのパラメーターは、対応する受信PPPV / FPPVの呼び出しパラメーターによって決定されます。 いずれかのPPPV / FPPVがトポロジ(パイプラインなど)に1つのレシーバーネイバーしかない場合、パラメーターは最も一般的です。 複数のレシーバーネイバーがある場合、パラメーターの1つが特別になります-アドレス。 トポロジグラフの同じ名前(1つのPPPV / FPPVに対応)のすべてのノードには番号が付けられているため、アドレスパラメーターは、受信PPPV / FPPVの名前に角括弧で囲まれた番号が続きます。 トポロジを静的に(ベクトル/パイプラインの特別な構成または任意のグラフのアークのリストによって)または半静的に-アークのリストが特別な(コード生成)挿入によって生成される場合-マクロモジュール(たとえば、PHPに似たイデオロギーを使用して記述できる-inserts generate)プログラムテキストの断片では、タスクに応じて任意の言語を使用できます(少なくともPHP、少なくともGNU Prolog)。 技術的には、特定の機能の呼び出しを使用したトポロジの通常の動的生成の可能性は排除されません。



完全な操作のために、チャネル/遅延変数、トランザクションメモリ、バリア、セマフォなどを常に追加で使用できます。



トポロジーが異なるいくつかの例を示します。



最小および最大ツリー要素のコンベヤー上の計算。



 chain[ARR_SIZE] TreeMax(TreeNode * Cur, reduction(max) DataItem & MaxResult) { static DataItem DummyMin; throw_last(Cur,DummyMin); if (Cur==Root) plan_group_parallelize; if (Cur->Left) plan_last(Cur->Left,MaxResult); if (Cur->Right) plan_last(Cur->Right,MaxResult); MaxResult = Cur->Data; } chain[ARR_SIZE] TreeMin(TreeNode * Cur, reduction(min) DataItem & MinResult) { if (Cur==Root) plan_group_parallelize; MinResult = Cur->Data; } void TreeMinMax(DataItem & Min, DataItem & Max) { Max.fill(0.0); Min.fill(1000.0); plan_parallel_chain(0,TreeMax(Root,Max),TreeMin(Root,Min)); }
      
      





特定の実装のパフォーマンスをテストするために、針と目のトポロジーを使用した例を使用できます。



 #include <iostream> using namespace std; bool stop; chain A(bool init) throw(bool init, int N) { stop = false; throw_first(false, 1); Sleep(2000); stop = true; } chain B(bool init, int N) throw(bool init, bool _stop, int N) { if (!init) { if (stop) throw_first(false, true, N); else throw_first(false, false, N+1); } } chain C(bool init, bool _stop, int N) throw(bool init, int N) { if (!init) { if (_stop) { cout<<N; plan_topology_quit(); /*    */ } else throw_first(false, N+1); } } int main() { plan_topology { /*    */ plan_parallel_chain(A(true)->B(true,0)->C(true,false,0)); /*    */ plan_parallel_reverse(C(true,false,0)->B(true,0)); /*    */ }/3; return 0; }
      
      





上記のすべては、C ++(Planning C)の拡張として実装されています。 上記に加えて、さらに興味深いものを実装する簡単なデモトランスレータがあります。



All Articles