1

私は基本的なコードを書くことによって CUDA を学ぼうとしています。うまくいけば、既存の C++ コードを (研究用に) CUDA に変換するためのより良い立場に立つことができます。

かなりの数の複素数操作を行う必要があるため、GPU カーネルで複素数の配列を実数で乗算するこの非常に基本的なコードを作成しました。

#include <complex>
#include <iostream>
#include <cmath>
#include "cuda.h"
#include "math.h"
#include "cuComplex.h"

#define n   5

using namespace std;

#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, char *file, int line, bool abort=true)
{
    if (code != cudaSuccess) 
    {
        fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
        if (abort) exit(code);
    }
}

__global__ void func( double *s, cuDoubleComplex *j, cuDoubleComplex *calc ) {

    int tid = blockIdx.x;

    calc[tid] = cuCmul(j[tid], make_cuDoubleComplex(*s, 0));

}

int main( void ) {


    cuDoubleComplex calc[n+1], *dev_j, *dev_calc;
    double *dev_s, s[n+1] = { 2.0, 2.0, 2.0, 2.0, 2.0 };
    //complex<double> j[n+1]
    cuDoubleComplex j[n+1];

    for (int i = 1; i <= n; i++) {
        j[i] = make_cuDoubleComplex(0, 5);
        cout << "\nJ cout = " << cuCreal(j[i]) << ", " << cuCimag(j[i]);
    }

    // allocate the memory on the GPU
    cudaMalloc( (void**)&dev_s, (n+1) * sizeof(double) );
    cudaMalloc( (void**)&dev_j, (n+1) * sizeof(double) );
    cudaMalloc( (void**)&dev_calc, (n+1) * sizeof(double) );

    cudaMemcpy( dev_s, s, (n+1) * sizeof(double), cudaMemcpyHostToDevice );
    cudaMemcpy( dev_j, j, (n+1) * sizeof(double), cudaMemcpyHostToDevice );

    func<<<n,1>>>( dev_s, dev_j, dev_calc );
    //kernel<<<1,1>>>(a_d);
    gpuErrchk( cudaPeekAtLastError() );
    gpuErrchk( cudaMemcpy(calc, dev_calc, (n+1) * sizeof(double), cudaMemcpyDeviceToHost) );

    //cudaMemcpy( calc, dev_calc, (n+1) * sizeof(double), cudaMemcpyDeviceToHost );

    for (int i = 1; i <= n; i++) {
        cout << "\nCALC cout = " << cuCreal(calc[i]) << ", " << cuCimag(calc[i]);
    }

    return 0;
}

最終的な答えは間違っています。期待値が得られない他のいくつかの場所も特定しました。

1) 次のコード行の後に、'j' のすべての要素に対して (0, 5i) の複雑な double 配列が必要でした。しかし、私はすべて0を取得しています。何故ですか?

j[i] = make_cuDoubleComplex(0, 5); 

2) cout を使用して配列を印刷できないのはなぜですか? 以下に示すコード行では、次のエラーが発生します。これらのオペランドに一致する演算子 "<<" はありません。printfを使用せずにこれを修正するにはどうすればよいですか?

cout << "\nJ = " << j[i];

3) GPU 関数 'func' は、最終的な答えとして (0, 10i) の配列を提供する必要があり、次のようなランダムな値を提供します。

CALC = -1.#QNAN0
CALC = -1.#QNAN0
CALC = -9255963134931783100000000...000.. etc
CALC = -9255963134931783100000000...000.. etc

4) 私の実際の研究では、複素数配列 'j' は cuDoubleComplex ではなく、complex(double) の形式で与えられます。関数 'func' を使用して、複合 (double) の 'j' 配列に同様の操作を行うことはできますか? そうでない場合、どのような選択肢がありますか?

よく説明できたと思いますが、追加の質問があれば遠慮なくお尋ねください。C++ だけでなく CUDA も初めてなので、よろしくお願いします :D

4

1 に答える 1

1

CUDA コードを書くとき、特に学習中または問題がある (期待どおりに動作しない) ときは、常にすべての CUDA API 呼び出しとカーネル呼び出しでcuda エラー チェックを行う必要があります。

コードに CUDA の機能エラーが実際にあるとは思いません (よくできました!) が、指摘する価値はあります。

あなたの質問のほとんどは、タイプをcuDoubleComplex適切に印刷していないという事実によるものです。printf ステートメントはfloatフォーマット パラメータ ( %f) を指定していますが、値を渡していませんfloat(値を渡していcuDoubleComplexます)。それは機能printfせず、エラー表示を出さずに奇妙な動作をします。

代わりに、次のようにしてみてください。

printf("\nJ = %f, %f", cuCreal(j[i]), cuCimag(j[i])); 

これらの関数 (cuCrealおよびcuCimag) は、数値の実数部分と虚数部分をcuComplex返し、それらを適切な型floatorとして返します。この場合、 todoubleからの暗黙のキャストは、実行していることに対して問題なく、(ただし、あまり良いプログラミング手法ではありません -- 代わりに、値に正しい書式指定子を使用してください)。doublefloatprintfprintfdouble

両方の printf ステートメントにその変更を加えると、期待どおりの結果が得られると思います。少なくとも、コードを実行したときにそうしました。それでもガベージが発生する場合は、CUDA GPU が正しく動作していない可能性があります。ここで、前述の CUDA エラー チェックを実行すると、問題が何であるかを発見するのに役立ちます。

に関するあなたの質問についてcoutは、答えは で何が起こっているかについての私の説明とほぼ同じprintfです。 coutタイプを理解していないためcuDoubleComplex、エラーがスローされます。を使用せずに修正したい場合は、上記のステートメントで示した変換関数を使用して、またはで表される個々の実数部と虚数部に をprintf変換します。cuDoubleComplexfloatdoubleprintf

最後の質問に関しては、complexデータをcuDoubleComplex型に変換するのは難しくありません。あなたが持っているユーティリティに基づいてそれを行うための変換関数を書いてcuComplex.h ください。

編集: フォローアップの質問に応えて、現在投稿されているコードにさらに 2 つのエラーがありました。

  1. dev_jdev_calcはタイプcuDoubleComplexですが、これらの量をサイズであるかのように実行cudaMallocしていました。次のコードでは、これらのエントリをに変更しました。cudaMemcpydoublesizeof(double)sizeof(cuDoubleComplex)
  2. 一般的に、C および C++ のインデックス作成は少し奇妙でした。通常、インデックスはゼロから始まります。最後の要素が適切に計算されないインデックス作成の問題がありました。すべてのインデックスをゼロベースに変更しました。

これが私のために働くあなたのコードの修正です:

//#include <complex>  // not necessary for this code
#include <iostream>
#include <cmath>
//#include "cuda.h"  // not necessary when compiling with nvcc
#include "math.h"
#include "cuComplex.h"

#define n   5

using namespace std;

#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, char *file, int line, bool abort=true)
{
    if (code != cudaSuccess)
    {
        fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
        if (abort) exit(code);
    }
}

__global__ void func( double *s, cuDoubleComplex *j, cuDoubleComplex *calc ) {

    int tid = blockIdx.x;

    calc[tid] = cuCmul(j[tid], make_cuDoubleComplex(*s, 0));

}

int main( void ) {


    cuDoubleComplex calc[n+1], *dev_j, *dev_calc;
    double *dev_s, s[n] = { 2.0, 2.0, 2.0, 2.0, 2.0 };
    //complex<double> j[n+1]
    cuDoubleComplex j[n];

    for (int i = 0; i < n; i++) {
        j[i] = make_cuDoubleComplex(0, 5);
        cout << "\nJ cout = " << cuCreal(j[i]) << ", " << cuCimag(j[i]);
    }

    // allocate the memory on the GPU
    cudaMalloc( (void**)&dev_s, (n) * sizeof(double) );
    cudaMalloc( (void**)&dev_j, (n) * sizeof(cuDoubleComplex) );
    cudaMalloc( (void**)&dev_calc, (n) * sizeof(cuDoubleComplex) );

    cudaMemcpy( dev_s, s, (n) * sizeof(double), cudaMemcpyHostToDevice );
    cudaMemcpy( dev_j, j, (n) * sizeof(cuDoubleComplex), cudaMemcpyHostToDevice );

    func<<<n,1>>>( dev_s, dev_j, dev_calc );
    //kernel<<<1,1>>>(a_d);
    gpuErrchk( cudaPeekAtLastError() );
    gpuErrchk( cudaMemcpy(calc, dev_calc, (n) * sizeof(cuDoubleComplex), cudaMemcpyDeviceToHost) );

    //cudaMemcpy( calc, dev_calc, (n+1) * sizeof(double), cudaMemcpyDeviceToHost );

    for (int i = 0; i < n; i++) {
        cout << "\nCALC cout = " << cuCreal(calc[i]) << ", " << cuCimag(calc[i]);
    }

    return 0;
}
于 2013-07-04T19:48:18.777 に答える