Cuda атомарность и условные переходы

Я пытаюсь написать CUDA версию кода serial как часть реализации периодического граничного условия в алгоритме молекулярной динамики. Идея состоит в том, что крошечную часть частиц, которые имеют нестандартные позиции, нужно вернуть обратно, используя один из двух ways, с ограничением на количество раз, которое я использую первым способом.

По сути, это сводится к следующему 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 только для небольшой части (около 12-15) из 30000 элементов x[i].

Обратите внимание, что последовательность i не важна, т. е. нет необходимости иметь 10 наименьшее i для использования 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);

}

Конечно, это неверно, потому что условие if для (line a) использует переменную, обновленную в (line b), которая может быть устаревшей. Это чем-то похоже на флаг изменения Cuda atomics, однако я не уверен, что и как использовать критические разделы помогут.

Есть ли способ убедиться, что count[0] обновлен, когда каждый поток проверяет условие if на (line a), не делая код слишком последовательным?


person h k    schedule 30.08.2017    source источник
comment
Я думаю, что ответ @Claude является хорошим и довольно простым и аккуратным, если вы можете терпеть постоянное увеличение count[0]. Чтобы воспроизвести точное поведение вашего последовательного кода (count увеличивается, пока не достигнет Nmax, а затем останавливается), должна быть возможность использовать пользовательский атом, построенный вокруг atomicCAS. Однако я подозреваю, что с точки зрения производительности это будет дороже, чем ответ Клода.   -  person Robert Crovella    schedule 30.08.2017


Ответы (1)


Просто каждый раз увеличивайте атомарный счетчик и используйте его возвратное значение в вашем тесте:

...
  if(x[i] > 2.9)
    {
       int oldCount = atomicAdd(&count[0],1);
       if(oldCount < Nmax[0])
         x[i] += 0.1; //first way
       else
         x[i] -= 0.2; //second way
    }
...

Если, как вы говорите, около 15 элементов превышают 2,9, а Nmax составляет около 10, будет небольшое количество «лишних» атомарных операций, накладные расходы на которые, вероятно, минимальны (и я не вижу, как сделать это более эффективно, что это не значит, что это невозможно...).

person Claude    schedule 30.08.2017