我試圖編寫CUDA
版本的serial
代碼作爲在分子動力學算法中實現週期性邊界條件的一部分。這個想法是,有一小部分位置在框外的粒子需要使用兩個ways
中的一個來重新使用,並限制我使用第一種方式的次數。Cuda原子和條件分支
本質上,它歸結爲以下MWE。我有一個數組x[N]
,其中N
很大,並且代碼如下serial
。
#include <cstdlib>
int main()
{
int N =30000;
double x[30000];
int Nmax = 10, count = 0;
for(int i = 0; i < N; i++)
x[i] = 1.0*(rand()%3);
for(int i = 0; i < N; i++)
{
if(x[i] > 2.9)
{
if(count < Nmax)
{
x[i] += 0.1; //first way
count++;
}
else
x[i] -= 0.2; //second way
}
}
}
請假定x[i] > 2.9
只爲x[i]
30000個元件的一小部分(約12-15)。
請注意,i
的順序並不重要,即不必使10
最低位使用x[i] += 0.1
,從而使算法具有潛在的可並行性。我認爲以下CUDA
版本MWE的,這與nvcc -arch sm_35 main.cu
編譯,其中main.cu
全文
#include <cstdlib>
__global__ void PeriodicCondition(double *x, int *N, int *Nmax, int *count)
{
int i = threadIdx.x+blockIdx.x*blockDim.x;
if(i < N[0])
{
if(x[i] > 2.9)
{
if(count[0] < Nmax[0]) //===============(line a)
{
x[i] += 0.1; //first way
atomicAdd(&count[0],1); //========(line b)
}
else
x[i] -= 0.2; //second way
}
}
}
int main()
{
int N = 30000;
double x[30000];
int Nmax = 10, count = 0;
srand(128512);
for(int i = 0; i < N; i++)
x[i] = 1.0*(rand()%3);
double *xD;
cudaMalloc((void**) &xD, N*sizeof(double));
cudaMemcpy(xD, &x, N*sizeof(double),cudaMemcpyHostToDevice);
int *countD;
cudaMalloc((void**) &countD, sizeof(int));
cudaMemcpy(countD, &count, sizeof(int),cudaMemcpyHostToDevice);
int *ND;
cudaMalloc((void**) &ND, sizeof(int));
cudaMemcpy(ND, &N, sizeof(int),cudaMemcpyHostToDevice);
int *NmaxD;
cudaMalloc((void**) &NmaxD, sizeof(int));
cudaMemcpy(NmaxD, &Nmax, sizeof(int),cudaMemcpyHostToDevice);
PeriodicCondition<<<938,32>>>(xD, ND, NmaxD, countD);
cudaFree(NmaxD);
cudaFree(ND);
cudaFree(countD);
cudaFree(xD);
}
當然,這是不正確的,因爲在(line a)
的if
條件使用在(line b)
更新一個變量,它可能不是最新的。這與Cuda atomics change flag有些類似,但是,我不確定是否以及如何使用關鍵部分會有所幫助。
有沒有辦法確保count[0]
是最新的,當每個線程檢查(line a)
上的if
條件時,不會使代碼太串行?
我認爲@Claude的答案是一個很好的答案,如果你可以容忍'count [0]'總是遞增的,那麼它就非常簡單和整齊。要重現序列代碼的確切行爲('count'遞增,直到達到'Nmax',然後停止),應該可以使用圍繞'atomicCAS'構建的自定義原子。然而,我懷疑在性能方面它會比克勞德的答案昂貴。 –