web-dev-qa-db-ja.com

cudaMalloc()がポインタからポインタを使用するのはなぜですか?

たとえば、cudaMalloc((void**)&device_array, num_bytes);

この質問は以前に 尋ねられた であり、応答は「cudaMallocがエラーコードを返すため」でしたが、私はそれを取得しません-ダブルポインタが戻ることと関係がありますエラーコード?単純なポインタでうまくいかないのはなぜですか?

私が書いたら

cudaError_t catch_status;
catch_status = cudaMalloc((void**)&device_array, num_bytes);

エラーコードはcatch_statusに配置され、割り当てられたGPUメモリへの単純なポインタを返すだけで十分ですよね。

43
rhyc

Cでは、データは値または シミュレートされた参照渡し (つまり、データへのポインター)を介して関数に渡すことができます。値によるものは一方向の方法論であり、ポインターによるものは関数とその呼び出し環境の間の双方向のデータフローを可能にします。

データ項目が関数パラメーターリストを介して関数に渡され、関数が元のデータ項目を変更して、変更された値が呼び出し元の環境に表示されることが期待される場合、これに対する正しいCメソッドはデータ項目を渡すことです。ポインタによる。 Cでは、ポインターを渡すときに、変更する項目のアドレスを取得して、ポインター(この場合はポインターへのポインター)を作成し、そのアドレスを関数に渡します。これにより、関数は呼び出し環境で元のアイテムを(ポインターを介して)変更できます。

通常、mallocはポインターを返します。呼び出し環境で代入を使用して、この戻り値を目的のポインターに割り当てることができます。 cudaMallocの場合、CUDA設計者は、ポインターではなく、戻り値を使用してエラーステータスを伝達することを選択しました。したがって、呼び出し環境でのポインターの設定は、関数に渡されるパラメーターの1つを介して、参照によって(つまり、ポインターによって)行われる必要があります。設定したいのはポインタ値なので、ポインタのアドレスを取得して(ポインタへのポインタを作成)、そのアドレスを渡す必要がありますcudaMalloc関数に。

69
Robert Crovella

ロバートの答えに追加しますが、最初に繰り返しますが、これはC APIです。つまり、関数内で(ポイントされているものだけでなく)ポインターの値を変更できる参照)をサポートしていません。。ロバート・クロベラの答えがこれを説明しています。また、Cは関数のオーバーロードもサポートしていないため、voidである必要があることに注意してください。

さらに、C++プログラム内でCAPIを使用する場合(ただし、これについては説明していません)、そのような関数をテンプレートでラップするのが一般的です。例えば、

template<typename T>
cudaError_t cudaAlloc(T*& d_p, size_t elements)
{
    return cudaMalloc((void**)&d_p, elements * sizeof(T));
}

上記のcudaAlloc関数の呼び出し方法には2つの違いがあります。

  1. Address-of演算子を使用せずに、デバイスポインタを直接渡します(&)それを呼び出すとき、そしてvoid型にキャストせずに。
  2. 2番目の引数elementsは、バイト数ではなく要素数になりました。 sizeof演算子はこれを容易にします。これは、要素を指定する方が間違いなく直感的であり、バイトについて心配する必要はありません。

例えば:

float *d = nullptr;  // floats, 4 bytes per elements
size_t N = 100;      // 100 elements

cudaError_t err = cudaAlloc(d,N);      // modifies d, input is not bytes

if (err != cudaSuccess)
    std::cerr << "Unable to allocate device memory" << std::endl;
9
chappjc

cudaMalloc関数のシグネチャは、例によってよりよく説明できると思います。それは基本的にそのバッファへのポインタ(ポインタへのポインタ)を介してバッファを割り当てることです、like次のメソッド:

int cudaMalloc(void **memory, size_t size)
{
    int errorCode = 0;

    *memory = new char[size];

    return errorCode;
}

ご覧のとおり、このメソッドはポインタへのmemoryポインタを取り、その上に新しく割り当てられたメモリを保存します。次に、エラーコードを返します(この場合は整数ですが、実際には列挙型です)。

cudaMalloc関数は、次のように設計できます。

void * cudaMalloc(size_t size, int * errorCode = nullptr)
{
    if(errorCode)
        errorCode = 0;

    char *memory = new char[size];

    return memory;
}

この2番目のケースでは、エラーコードは暗黙的にnullに設定されたポインタを介して設定されます(人々がエラーコードをまったく気にしない場合)。次に、割り当てられたメモリが返されます。

最初の方法は、現在実際のcudaMallocと同じように使用できます。

float *p;
int errorCode;
errorCode = cudaMalloc((void**)&p, sizeof(float));

2番目のものは次のように使用できますが:

float *p;
int errorCode;
p = (float *) cudaMalloc(sizeof(float), &errorCode);

これらの2つのメソッドは機能的には同等ですが、署名が異なり、cudaの人々は最初のメソッドを選択し、エラーコードを返し、ポインターを介してメモリを割り当てますが、ほとんどの人は2番目のメソッドはより良い選択。

4
meJustAndrew