我有一個相當複雜的C++類,它具有類myObj.fun(x,y,z)
的功能。我想在一個3D網格點上的GPU上調用它。只能將一個類傳遞給CUDA內核進行並行評估?
我的高層次的問題是:通過myObj
和大量的點到GPU是一件簡單的事情嗎?由於工作原因,我避免創建此函數的CUDA實現,但對我而言,這可能非常簡單。
同樣,這是一個非常高層次的問題,所以「是的,這很容易」或「不,它更復雜」是受歡迎的,儘管有點方向也會有所幫助。
我有一個相當複雜的C++類,它具有類myObj.fun(x,y,z)
的功能。我想在一個3D網格點上的GPU上調用它。只能將一個類傳遞給CUDA內核進行並行評估?
我的高層次的問題是:通過myObj
和大量的點到GPU是一件簡單的事情嗎?由於工作原因,我避免創建此函數的CUDA實現,但對我而言,這可能非常簡單。
同樣,這是一個非常高層次的問題,所以「是的,這很容易」或「不,它更復雜」是受歡迎的,儘管有點方向也會有所幫助。
可以在GPU上使用類/對象,包括它們的方法(例如fun()
)。這樣的類至少必須具有用__host__ __device__
修飾的方法,但代碼重構可能不會比這更多地涉及。然而,這樣的方法(像沒有任何重構的其他代碼一樣)可能不會訪問GPU的任何並行功能。最基本的比較是,在單個CPU線程中運行的方法會在單個GPU線程中運行。這通常不會更快,如果您只是將單個對象傳遞給GPU並在GPU上運行相同的單線程代碼(在單個GPU線程中),則速度通常會更慢。
一種可能的策略是,如果你有很多這些對象,或者在你的情況下有相當多的「點」,代表要獨立完成的工作,那麼你可以通過每一個(對象或點)到GPU線程,並以這種方式處理它們,以便實現GPU所喜歡的大規模多線程操作。理想情況下,你將有10,000或更多的點來處理這種方式。
由於各種原因,這仍然不是最有效的GPU使用方式,其中一個原因與高效的數據訪問有關,另一個原因與(可能的)線程分歧有關。儘管如此,有些人確實追求這種「簡單」,「不同尋常的並行」的代碼移植方法,偶爾也會有趣的加速。
根據您的實際代碼,如果您以允許相鄰線程訪問相鄰數據的方式將點傳遞給GPU,對於訪問點的每個操作,您可能會看到有吸引力的結果。期望你可以以這種方式獲得有吸引力的加速是相當合理的,可能只需要相對較少的代碼重構,但要注意數據組織以實現最佳GPU訪問。
這裏是一個完全樣例:
$ cat t30.cu
#include <iostream>
#include <cstdlib>
const int dsize = 3;
const int nTPB = 256;
const int rng = 8;
class myclass
{
int increment;
public:
myclass(int _incr): increment(_incr) {};
// methods callable on the device need the __device__ decoration
__host__ __device__ void fun(int &x, int &y, int &z){
x += increment;
y += increment;
z += increment;}
};
// this is the actual device routine that is run per thread
__global__ void mykernel(myclass obj, int *dx, int *dy, int *dz, int dsize){
int idx = threadIdx.x+blockDim.x*blockIdx.x; // figure out which thread we are
if (idx < dsize)
obj.fun(dx[idx], dy[idx], dz[idx]); // apply method
}
int main(){
// allocate host data
int *p_x, *p_y, *p_z, *d_x, *d_y, *d_z;
p_x = new int[dsize];
p_y = new int[dsize];
p_z = new int[dsize];
// allocate device data
cudaMalloc(&d_x, dsize*sizeof(int));
cudaMalloc(&d_y, dsize*sizeof(int));
cudaMalloc(&d_z, dsize*sizeof(int));
// initialize host data
std::cout << "Before:" << std::endl;
for (int i = 0; i < dsize; i++){
p_x[i] = rand()%rng;
p_y[i] = rand()%rng;
p_z[i] = rand()%rng;
std::cout << p_x[i] << "," << p_y[i] << "," << p_z[i] << std::endl;}
// copy to device
cudaMemcpy(d_x, p_x, dsize*sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_y, p_y, dsize*sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_z, p_z, dsize*sizeof(int), cudaMemcpyHostToDevice);
// instantiate object on host
myclass test(1);
// copy object to device as kernel parameter
mykernel<<<(dsize+nTPB-1)/nTPB, nTPB>>>(test, d_x, d_y, d_z, dsize);
// copy data back to host
cudaMemcpy(p_x, d_x, dsize*sizeof(int), cudaMemcpyDeviceToHost);
cudaMemcpy(p_y, d_y, dsize*sizeof(int), cudaMemcpyDeviceToHost);
cudaMemcpy(p_z, d_z, dsize*sizeof(int), cudaMemcpyDeviceToHost);
std::cout << "After:" << std::endl;
for (int i = 0; i < dsize; i++){
std::cout << p_x[i] << "," << p_y[i] << "," << p_z[i] << std::endl;}
return 0;
}
$ nvcc -o t30 t30.cu
$ ./t30
Before:
7,6,1
3,1,7
2,4,1
After:
8,7,2
4,2,8
3,5,2
$
爲了表達簡潔,我省略了proper cuda error checking但我總是建議你使用它時,你正在開發CUDA代碼。