1

原則

そのような単純な計算は、精巧に並列化する価値がないことはわかっています。これはそのような例であり、数学演算は、より興味深い計算の単なるプレースホルダーです。

【疑似コード】

var id = 0,
do {
    id = getGlobalId();
    output[id] = input[id] * input[id];
} while (inRange(id) && output[id] !== 25);

最も特殊な表現は次のとおりoutput[id] !== 25です。つまり、次のことを意味します: input4 つの要素 (この順序で) がある場合:[8, 5, 2, 9]outputある必要があり、 or[64, 25]の 2 乗は のアイテムとして使用されません(はandのためであるため)。29outputoutput[id] !== 25trueid = 1input[id] = 5

このコードを最適化する場合、input[id]事前に (2 番目の条件を証明せずにwhile) の 2 乗を計算することをお勧めしますが、結果が後で関連するという保証はありません (前の計算の結果が 25 だった場合)。 、現在の計算の結果は興味深いものではありません)。

一般化して、私は計算結果 output[id]( output[id] = calculateFrom(input[id]);) がすべてに関連していないid可能性があるケースについて話しています。結果 ( ) の必要性は、output[id]別の計算の結果に依存します。

私の目標

OpenCLカーネルとキューを使用して、このループを可能な限り並列かつ高性能に実行したいと考えています。

私のアイデア

  • 私は考えました:そのようなdo...whileループを並列化できるようにするには、事前にいくつかの計算(output[id] = calculateFrom(input[id]);)を同時に行う必要があります(結果output[id]が役立つかどうかはわかりません)。そして、前の結果が だった場合、結果は25単純output[id]に拒否されます。

  • の確率を考えたほうがいいかもしれませんoutput[id] !== 25。確率が非常に高い場合、結果が拒否される可能性があるため、事前に多くの計算を行うことはありません。確率が絶対に低い場合は、事前により多くの計算を行う必要があります。

  • 処理ユニットの現在のステータスをリッスンする必要があります。すでに過緊張している場合は、重要でない事前計算を行うべきではありません。しかし、事前計算を処理するのに十分なリソースがある場合は、そうではありません。- 理由: 事前計算と以前の計算 (これらの事前計算が依存している) が同時に処理される場合、事前計算の追加によって以前の計算が遅くなる可能性もあります - (私の2番目の質問を参照してください)

私の質問

  1. そのようなプログラムを並列化することは賢明ですか、それとも高性能ですか?
  2. プロセッシング ユニットに事前計算処理を行うのに十分なリソースがあるかどうかは、どの基準に基づいて判断すればよいですか? または:プロセッシング ユニットに負荷がかかりすぎているかどうかを確認するにはどうすればよいですか?
  3. do...whileそのようなsを並列化するための他の計画について知っていますか? それについて何か考えはありますか?

私があなたに伝えたいことが常に明確であることを願っています。そうでない場合は、私の質問にコメントしてください。- ご回答とご協力ありがとうございます。

4

1 に答える 1

1

単一のワークグループを使用し、デバイスのローカルメモリを利用する場合、この作業は簡単に並行して実行できます。openclの仕様によると、この目的のために少なくとも16kbのメモリが利用可能になります。

いくつかの疑似oclコード:

__kernel doWhileTest(...){

  local int outputBuff[groupSize];
  local int loopBreak[groupsize];

  loopBreak[localId] = 0;
  barrier();

  for(int i = localId;loopBreak[0]==0;i+=groupSize){
    if(i<maxIndex){
      //do interesting calculation on globalInputValues[i]
      //save result to outputBuff[localId]
      //condition failed? set loopBreak[localId] to 1
    }else{
      //set loopBreak condition here as well
    }

    barrier();
    if(localId ==0){
      //loop through all loopBreak values (for j = 0..groupSize)
      //0? copy outputBuff[j] to global memory (globalInputValues[i+j])
      //1? set loopBreak[0] to 1 as well and break this inner loop
    }
    barrier();
  }

  //optional: write some default value to the remaining global buffer, or save a count of the valid outputs

}

上記のforループは、インデックスがforステートメントではなくループ内でmaxIndexと比較されているという点で、少し奇妙に見えるかもしれません。これは、すべての作業項目がループ内の両方のバリアに到達する必要があるためです。これは、一部の作業項目が早期に発生した場合(つまり、maxIndexがgroupSizeの倍数でない場合)は不可能です。関連する質問再:障壁

また、ここではグローバルアトミック書き込みを避けています。これは、特にデータの全範囲を一度に読み取り/書き込みするため、ローカル共有データ/スクラッチパッドメモリほどのパフォーマンスが得られないためです。

上記の設計を使用すると、処理をやりすぎずに、値の一部を事前に計算することができます。ループを並列にしながら、最大でgroupSizeの結果のみを破棄します。これらのワークグループの多くを同時に開始することもできますが、グローバルな値やブレーク条件ではなく、それぞれが独自のデータに基づいてブレークアウトする(またはしない)でしょう。この場合、groupSizeを低く保つことができるので、壊れないグループは、クランチに多くの時間を費やすことはありません。(グローバルブレーク値の使用も試さないでください。これを試してGPUをスピンロックし、死の白い画面が表示されました。)

于 2012-09-05T11:21:25.487 に答える