43

私は、配列に約1,000万から3,000万の浮動小数点値を含む統計アプリケーションに取り組んでいます。

ネストされたループ内の配列に対して、異なるが独立した計算を実行するいくつかのメソッド。次に例を示します。

Dictionary<float, int> noOfNumbers = new Dictionary<float, int>();

for (float x = 0f; x < 100f; x += 0.0001f) {
    int noOfOccurrences = 0;

    foreach (float y in largeFloatingPointArray) {
        if (x == y) {
            noOfOccurrences++;
        }
    }
    noOfNumbers.Add(x, noOfOccurrences);
}

現在のアプリケーションはC#で記述されており、Intel CPUで実行され、完了するまでに数時間かかります。GPUプログラミングの概念とAPIの知識がないので、質問は次のとおりです。

  • GPUを利用してそのような計算を高速化することは可能ですか(そしてそれは理にかなっていますか)?
  • はいの場合:誰かがチュートリアルを知っているか、サンプルコードを入手していますか(プログラミング言語は関係ありません)?
4

5 に答える 5

88

GPUバージョンの更新

__global__ void hash (float *largeFloatingPointArray,int largeFloatingPointArraySize, int *dictionary, int size, int num_blocks)
{
    int x = (threadIdx.x + blockIdx.x * blockDim.x); // Each thread of each block will
    float y;                                         // compute one (or more) floats
    int noOfOccurrences = 0;
    int a;
    
    while( x < size )            // While there is work to do each thread will:
    {
        dictionary[x] = 0;       // Initialize the position in each it will work
        noOfOccurrences = 0;    

        for(int j = 0 ;j < largeFloatingPointArraySize; j ++) // Search for floats
        {                                                     // that are equal 
                                                             // to it assign float
           y = largeFloatingPointArray[j];  // Take a candidate from the floats array 
           y *= 10000;                      // e.g if y = 0.0001f;
           a = y + 0.5;                     // a = 1 + 0.5 = 1;
           if (a == x) noOfOccurrences++;    
        }                                      
                                                    
        dictionary[x] += noOfOccurrences; // Update in the dictionary 
                                          // the number of times that the float appears 

    x += blockDim.x * gridDim.x;  // Update the position here the thread will work
    }
}

これは、ラップトップでテストしているため、小さな入力用にテストしたばかりです。それにもかかわらず、それは機能していますが、さらにテストが必要です。

UPDATE順次バージョン

30,000,000 要素を持つ配列のアルゴリズムを 20 秒未満で実行するこの単純なバージョンを実行しました (データを生成する関数にかかる時間を含む)。

この単純なバージョンでは、最初に float の配列を並べ替えます。その後、ソートされた配列を調べて、指定されたものが配列に出現する回数をチェックし、valueこの値を出現回数とともに辞書に入れます。

私が使っsortedた の代わりに mapを使うことができますunordered_map

コードは次のとおりです。

#include <stdio.h>
#include <stdlib.h>
#include "cuda.h"
#include <algorithm>
#include <string>
#include <iostream>
#include <tr1/unordered_map>


typedef std::tr1::unordered_map<float, int> Mymap;


void generator(float *data, long int size)
{
    float LO = 0.0;
    float HI = 100.0;
    
    for(long int i = 0; i < size; i++)
        data[i] = LO + (float)rand()/((float)RAND_MAX/(HI-LO));
}

void print_array(float *data, long int size)
{

    for(long int i = 2; i < size; i++)
        printf("%f\n",data[i]);
    
}

std::tr1::unordered_map<float, int> fill_dict(float *data, int size)
{
    float previous = data[0];
    int count = 1;
    std::tr1::unordered_map<float, int> dict;
    
    for(long int i = 1; i < size; i++)
    {
        if(previous == data[i])
            count++;
        else
        {
          dict.insert(Mymap::value_type(previous,count));
          previous = data[i];
          count = 1;         
        }
        
    }
    dict.insert(Mymap::value_type(previous,count)); // add the last member
    return dict;
    
}

void printMAP(std::tr1::unordered_map<float, int> dict)
{
   for(std::tr1::unordered_map<float, int>::iterator i = dict.begin(); i != dict.end(); i++)
  {
     std::cout << "key(string): " << i->first << ", value(int): " << i->second << std::endl;
   }
}


int main(int argc, char** argv)
{
  int size = 1000000; 
  if(argc > 1) size = atoi(argv[1]);
  printf("Size = %d",size);
  
  float data[size];
  using namespace __gnu_cxx;
  
  std::tr1::unordered_map<float, int> dict;
  
  generator(data,size);
  
  sort(data, data + size);
  dict = fill_dict(data,size);
  
  return 0;
}

マシンにライブラリ スラストがインストールされている場合は、これを使用する必要があります。

#include <thrust/sort.h>
thrust::sort(data, data + size);

これの代わりに

sort(data, data + size);

確かに速くなります。

元の投稿

私は、1,000 万から 3,000 万の浮動小数点値を含む大きな配列を持つ統計アプリケーションに取り組んでいます。

GPU を利用してそのような計算を高速化することは可能ですか (それは理にかなっていますか)?

はい、そうです。1 か月前、GPU 上で完全な分子動力学シミュレーションを実行しました。粒子のペア間の力を計算したカーネルの 1 つは、パラメーター6配列としてそれぞれ500,000double を受け取り、合計で3100 万 doubleになり(22 MB)ました。

したがって、グローバル メモリの30約 100 万個の浮動小数点数を配置する予定がある場合114 MBは、問題ありません。

あなたの場合、計算の数が問題になる可能性はありますか? 分子動力学 (MD) での私の経験に基づいて、私はノーと言います。シーケンシャル MD バージョンは完了するまでに約25数時間かかりますが、GPU バージョンは45数分かかりました。アプリケーションに数時間かかったとおっしゃいましたが、コード例に基づいて、MDよりもソフトに見えます。

力の計算例は次のとおりです。

__global__ void add(double *fx, double *fy, double *fz,
                    double *x, double *y, double *z,...){
   
     int pos = (threadIdx.x + blockIdx.x * blockDim.x); 
      
     ...
     
     while(pos < particles)
     {
     
      for (i = 0; i < particles; i++)
      {
              if(//inside of the same radius)
                {
                 // calculate force
                } 
       }
     pos += blockDim.x * gridDim.x;  
     }        
  }

CUDA のコードの簡単な例は、2 つの 2D 配列の合計です。

C:

for(int i = 0; i < N; i++)
    c[i] = a[i] + b[i]; 

CUDA では:

__global__ add(int *c, int *a, int*b, int N)
{
  int pos = (threadIdx.x + blockIdx.x)
  for(; i < N; pos +=blockDim.x)
      c[pos] = a[pos] + b[pos];
}

CUDA では、基本的にそれぞれ繰り返し処理し、各スレッドに割り当てます。

1) threadIdx.x + blockIdx.x*blockDim.x;

各ブロックにはIDfrom0からN-1(N はブロックの最大数) があり、各ブロックにはfrom からまで'X'のスレッドがいくつかあります。ID0X-1

  1. 各スレッドがそのスレッドとスレッドが含まれるブロックに基づいて計算する for ループの反復を提供します。blockDim.x は、ブロックが持つスレッドの数です。IDID

したがって、それぞれに10スレッド とがある 2 つのブロックがある場合、次のようになりますN=40

Thread 0 Block 0 will execute pos 0
Thread 1 Block 0 will execute pos 1
...
Thread 9 Block 0 will execute pos 9
Thread 0 Block 1 will execute pos 10
....
Thread 9 Block 1 will execute pos 19
Thread 0 Block 0 will execute pos 20
...
Thread 0 Block 1 will execute pos 30
Thread 9 Block 1 will execute pos 39

現在のコードを見て、CUDA でコードがどのように見えるかについて、このドラフトを作成しました。

__global__ hash (float *largeFloatingPointArray, int *dictionary)
    // You can turn the dictionary in one array of int
    // here each position will represent the float
    // Since  x = 0f; x < 100f; x += 0.0001f
    // you can associate each x to different position
    // in the dictionary:

    // pos 0 have the same meaning as 0f;
    // pos 1 means float 0.0001f
    // pos 2 means float 0.0002f ect.
    // Then you use the int of each position 
    // to count how many times that "float" had appeared 


   int x = blockIdx.x;  // Each block will take a different x to work
    float y;
    
while( x < 1000000) // x < 100f (for incremental step of 0.0001f)
{
    int noOfOccurrences = 0;
    float z = converting_int_to_float(x); // This function will convert the x to the
                                          // float like you use (x / 0.0001)

    // each thread of each block
    // will takes the y from the array of largeFloatingPointArray
    
    for(j = threadIdx.x; j < largeFloatingPointArraySize; j += blockDim.x)
    {
        y = largeFloatingPointArray[j];
        if (z == y)
        {
            noOfOccurrences++;
        }
    }
    if(threadIdx.x == 0) // Thread master will update the values
      atomicAdd(&dictionary[x], noOfOccurrences);
    __syncthreads();
}

atomicAdd異なるブロックの異なるスレッドがnoOfOccurrences同時に書き込み/読み取りを行う可能性があるため、使用する必要があるため、相互排除を確保する必要があります

これは 1 つのアプローチにすぎません。外側のループの反復をブロックではなくスレッドに割り当てることもできます。

チュートリアル

Dr Dobbs Journal シリーズのCUDA: Rob Farmer による大衆向けのスーパーコンピューティングは優れており、14 回の記事でほぼすべてをカバーしています。それはまたかなり穏やかに始まるので、かなり初心者に優しい.

その他:

最後の項目を見ると、CUDA を学ぶためのリンクがたくさんあります。

OpenCL: OpenCL チュートリアル | マックリサーチ

于 2012-11-15T07:24:10.250 に答える
11

並列処理や GPGPU についてはよくわかりませんが、この特定の例では、入力配列を 100 万回ループするのではなく、1 回パスすることで多くの時間を節約できます。大規模なデータ セットでは、通常、可能であれば 1 回のパスで処理したいと考えるでしょう。複数の独立した計算を行っている場合でも、それが同じデータセット上にある場合は、同じパスでそれらすべてを実行すると速度が向上する可能性があります。その方法で参照の局所性が向上するためです。しかし、コードの複雑さが増すため、それだけの価値はないかもしれません。

さらに、そのように浮動小数点数に少量を繰り返し追加したくない場合は、丸め誤差が加算され、意図した結果が得られません。以下のサンプルに if ステートメントを追加して、入力が繰り返しのパターンに一致するかどうかを確認しましたが、実際にそれが必要ない場合は省略してください。

C# はわかりませんが、サンプルのシングル パス実装は次のようになります。

Dictionary<float, int> noOfNumbers = new Dictionary<float, int>();

foreach (float x in largeFloatingPointArray)
{
    if (math.Truncate(x/0.0001f)*0.0001f == x)
    {
        if (noOfNumbers.ContainsKey(x))
            noOfNumbers.Add(x, noOfNumbers[x]+1);
        else
            noOfNumbers.Add(x, 1);
    }
}

お役に立てれば。

于 2012-11-09T03:37:54.943 に答える
9

GPU を利用してそのような計算を高速化することは可能ですか (それは理にかなっていますか)?

  • 確かにYESです。この種のアルゴリズムは通常、 GPU が得意とする大量のデータ並列処理の理想的な候補です。

はいの場合: チュートリアルを知っている人やサンプル コードを入手した人はいますか (プログラミング言語は関係ありません)。

  • GPGPU を使いたい場合は、CUDAまたはOpenCLの 2 つの選択肢があります。

    CUDA は多くのツールで成熟していますが、NVidia GPU 中心です。

    OpenCL は、NVidia と AMD GPU、および CPU でも動作する標準です。だからあなたは本当にそれを支持するべきです。

  • チュートリアルについては、 Rob Farberによる CodeProject に関する優れたシリーズがあります: http://www.codeproject.com/Articles/Rob-Farber#Articles

  • 特定のユースケースでは、OpenCL を使用したヒストグラム作成のサンプルが多数あります (多くは画像ヒストグラムですが、原則は同じであることに注意してください)。

  • C# を使用すると、OpenCL.NetClooなどのバインディングを使用できます。

  • 配列が大きすぎて GPU メモリに格納できない場合は、配列をブロック分割して、各部分の OpenCL カーネルを簡単に再実行できます。

于 2012-11-13T09:56:06.693 に答える
6

上記の投稿者による提案に加えて、適切な場合は TPL (タスク並列ライブラリ) を使用して、複数のコアで並列に実行します。

上記の例では Parallel.Foreach と ConcurrentDictionary を使用できますが、配列がチャンクに分割され、それぞれがディクショナリを生成するチャンクに分割され、その後単一のディクショナリに削減される、より複雑な map-reduce 設定では、より良い結果が得られます。

すべての計算が GPU 機能に正しくマップされているかどうかはわかりませんが、とにかく map-reduce アルゴリズムを使用して計算を GPU コアにマップし、部分的な結果を 1 つの結果に減らす必要があります。あまりなじみのないプラットフォームに移る前に、CPU でそれを行うこともできます。

于 2012-11-09T03:49:05.993 に答える
6

「largerFloatingPointArray」の値をメモリから取得する必要があることを考えると、GPU の使用が適切かどうかはわかりません。私の理解では、GPU は自己完結型の計算により適しています。

この単一プロセス アプリケーションを多くのシステムで実行される分散アプリケーションに変え、アルゴリズムを微調整することで、使用可能なシステムの数に応じて、処理速度が大幅に向上すると思います。

古典的な「分割統治」アプローチを使用できます。私が取る一般的なアプローチは次のとおりです。

1 つのシステムを使用して、'largeFloatingPointArray' をハッシュ テーブルまたはデータベースに前処理します。これは、1 回のパスで実行されます。浮動小数点値をキーとして使用し、配列内の出現回数を値として使用します。最悪のシナリオは、各値が 1 回だけ発生することですが、それはありそうもありません。アプリケーションが実行されるたびに largeFloatingPointArray が変化し続ける場合、インメモリ ハッシュ テーブルは理にかなっています。静的な場合は、テーブルを Berkeley DB などのキー値データベースに保存できます。これを「ルックアップ」システムと呼びましょう。

別のシステムでは、それを「メイン」と呼び、作業のチャンクを作成し、作業項目を N 個のシステムに「分散」させ、結果が利用可能になったら「収集」します。たとえば、作業項目は、システムが作業する範囲を示す 2 つの数字のような単純なものである可能性があります。システムが作業を完了すると、オカレンスの配列が返され、別の作業のチャンクで作業する準備が整います。

largeFloatingPointArray を反復し続けないため、パフォーマンスが向上します。検索システムがボトルネックになった場合、必要な数のシステムに複製できます。

十分な数のシステムが並行して動作していれば、処理時間を数分に短縮できるはずです。

私は、システム内の複数の「システムオンチップ」モジュールを使用して構築される/または構築される、マイクロサーバーと呼ばれることが多いメニーコアベースのシステムを対象とした、C での並列プログラミング用のコンパイラに取り組んでいます。ARM モジュール ベンダーには、Calxeda、AMD、AMCC などがあります。おそらく、Intel も同様の製品を提供するでしょう。

このようなアプリケーションに使用できるバージョンのコンパイラーが動作しています。C 関数プロトタイプに基づくコンパイラは、システム間でプロセス間通信コード (IPC) を実装する C ネットワーク コードを生成します。利用可能な IPC メカニズムの 1 つは、socket/tcp/ip です。

分散ソリューションの実装についてサポートが必要な場合は、喜んでご相談させていただきます。

2012 年 11 月 16 日追加。

アルゴリズムについてもう少し考えてみたところ、これは 1 回のパスで実行できるはずだと思います。それは C で書かれており、あなたが持っているものと比較して非常に速いはずです。

/*
 * Convert the X range from 0f to 100f in steps of 0.0001f
 * into a range of integers 0 to 1 + (100 * 10000) to use as an
 * index into an array.
 */

#define X_MAX           (1 + (100 * 10000))

/*
 * Number of floats in largeFloatingPointArray needs to be defined
 * below to be whatever your value is.
 */

#define LARGE_ARRAY_MAX (1000)

main()
{
    int j, y, *noOfOccurances;
    float *largeFloatingPointArray;

    /*
     * Allocate memory for largeFloatingPointArray and populate it.
     */

    largeFloatingPointArray = (float *)malloc(LARGE_ARRAY_MAX * sizeof(float));    
    if (largeFloatingPointArray == 0) {
        printf("out of memory\n");
        exit(1);
    }

    /*
     * Allocate memory to hold noOfOccurances. The index/10000 is the
     * the floating point number.  The contents is the count.
     *
     * E.g. noOfOccurances[12345] = 20, means 1.2345f occurs 20 times
     * in largeFloatingPointArray.
     */

    noOfOccurances = (int *)calloc(X_MAX, sizeof(int));
    if (noOfOccurances == 0) {  
        printf("out of memory\n");
        exit(1);
    }

    for (j = 0; j < LARGE_ARRAY_MAX; j++) {
        y = (int)(largeFloatingPointArray[j] * 10000);
        if (y >= 0 && y <= X_MAX) {
            noOfOccurances[y]++;
        }   
    }
}
于 2012-11-12T05:13:37.597 に答える