Атомарные операции над общей памятью в CUDA - PullRequest
0 голосов
/ 10 октября 2011

Я использую GTX 280, который имеет вычислительные возможности 1.3 и поддерживает атомарные операции с общей памятью.Я использую cuda SDK 2.2 и VS 2005. В моей программе мне нужно широко использовать атомарные операции, потому что другого пути просто нет.

Один из примеров - мне нужно вычислить текущую сумму массива и найтииз индекса, где сумма превышает заданное значение отсечения.Для этого я использую вариант алгоритма scan и atomicMin для хранения индекса, пока значение меньше порогового значения.Таким образом, таким образом, в конце разделяемая память будет иметь индекс, где значение чуть меньше порогового значения.

Это всего лишь один компонент ядра, и в вызове ядра есть много похожих блоков кода.

У меня 3 проблемы

  1. Во-первых, я не смог скомпилировать код, поскольку он говорит, что атомарные операции не определены, я искал, но не нашел, какой файл мне нужнодобавить.
  2. Во-вторых, мне каким-то образом удалось скомпилировать код, скопировав его в код, предоставленный CUDA SDK, но затем он говорит, что атомарные операции не поддерживаются в общей памяти, где он выполняется вследующая программа
  3. Даже когда я работал над взломом, предоставив -arch sm_12 при компиляции командной строки, фрагмент кода, использующий эти атомарные операции, занимал очень много времени.

Я полагаю, что в худшем случае я должен получить какую-то скорость, потому что атомных операций не очень много, и я использую 1 блок 16x16.К сожалению, последовательный код работает в 10 раз быстрее.



Ниже, я публикую код ядра *, этот вызов ядра кажется узким местом, если кто-то может помочь мне оптимизировать, тогда было бы неплохо.Серийный код просто выполняет эти действия в последовательном порядке.Я использую конфигурацию блока 16 X 16.

Код кажется длинным, но на самом деле он содержит блок кода if и блок кода, которые выполняют почти одну и ту же задачу, но не могут быть объединены.

#define limit (int)(log((float)256)/log((float)2))

// This receives a pointer to an image, some variables and 4 more arrays cont(of size 256) vars(some constants), lim and buf(of image size)
// block configuration 1 block of 16x16

__global__ void kernel_Main(unsigned char* in, int height,int width, int bs,int th, double cutoff, uint* cont,int* vars, unsigned int* lim,unsigned int* buf)
{  

    int j = threadIdx.x;
    int i = threadIdx.y;

    int k = i*blockDim.x+j;


    __shared__  int prefix_sum[256];  
    __shared__  int sum_s[256];
    __shared__  int ary_shared[256];
    __shared__  int he_shared[256];

    // this is the threshold
    int cutval = (2*width*height)*cutoff;
    prefix_sum[k] = cont[k];

    int l;
    // a variant of scan algorithm 
    for(l=0;l<=limit;l++)
    {
        sum_s[k]=prefix_sum[k];

        if(k >= (int)pow((float)2,(float)l))
        {  
            prefix_sum[k]+=sum_s[k-(int)pow((float)2,(float)l)];
            // Find out the minimum index for which the cummulative sum crosses threshold
            if(prefix_sum[k] > cutval)
            {
                atomicMin(&vars[cut],k);
            }
        }
        __syncthreads();
    }

    // The first thread will store the value in global array
    if(k==0)
    {
        vars[cuts]=prefix_sum[vars[cut]];
    }
    __syncthreads();


    if(vars[n])
    {
        // bs = 7 in this case
        if(i<bs && j<bs)
        {
            // using atomic add because the index could be same for 2 different threads
            atomicAdd(&ary_shared[in[i*(width) + j]],1);  
        }
        __syncthreads();


        int minth = 1>((bs*bs)/20)? 1: ((bs*bs)/20);
        prefix_sum[k] = ary_shared[k];
        sum_s[k] = 0;

        // Again prefix sum

        int l;
        for(l=0;l<=limit;l++)
        {
            sum_s[k]=prefix_sum[k];

            if(k >= (int)pow((float)2,(float)l))
            {  
                prefix_sum[k]+=sum_s[k-(int)pow((float)2,(float)l)];
                // Find out the minimum index for which the cummulative sum crosses threshold
                if(prefix_sum[k] > minth)
                {
                    atomicMin(&vars[hmin],k);
                }
            }
            __syncthreads();
        }

         // set the maximum value here
        if(k==0)
        {
            vars[hminc]=prefix_sum[255];
            // because we will always overshoot by 1
            vars[hmin]--;
        }

        __syncthreads();

        int maxth = 1>((bs*bs)/20)? 1: ((bs*bs)/20);
        prefix_sum[k] = ary_shared[255-k];

        for(l=0;l<=limit;l++)
        {
            sum_s[k]=prefix_sum[k];

            if(k >= (int)pow((float)2,(float)l))
            {  
                prefix_sum[k]+=sum_s[k-(int)pow((float)2,(float)l)];
                // Find out the minimum index for which the cummulative sum crosses threshold
                if(prefix_sum[k] > maxth)
                {
                    atomicMin(&vars[hmax], k);
                }
            }
             __syncthreads();
        }
         // set the maximum value here

         if(k==0)
         {
            vars[hmaxc]=prefix_sum[255];
            vars[hmax]--;
            vars[hmax]=255-vars[hmax];

         }
        __syncthreads();



        int rng = vars[hmax] - vars[hmin];
        if(rng >= vars[cut])
        {
          if( k <= vars[hmin] )
                he_shared[k] = 0;
          else if( k >= vars[hmax])
                he_shared[k] = 255;
          else
                he_shared[k] = (255 * (k - vars[hmin])) / rng;
        }
         __syncthreads();

        // only 7x7 = 49 threads will do this
        if(i>0 && i<=bs && j>0 && j<=bs)
        {
           int base = (vars[oy]*width+vars[ox])+ (i-1)*width + (j-1);

           if(rng >= vars[cut])
           {
              int value = he_shared[in[base]];
              buf[base]+=value;
              lim[base]++;
           }
           else
           {
              buf[base]+=255;
              lim[base]++;
           }
        }

        if(k==0)
            vars[n]--;

        __syncthreads();   


    }// if(n) block closes here

    while(vars[n])
    {


        if(k==0)
        {
            if( vars[ox]==0 && vars[d1] ==3 )
                vars[d1] = 0; // l2r
            else if( vars[ox]==0 && vars[d1]==2 )
                vars[d1] = 3; // l u2d
            else if( vars[ox]==width-bs && vars[d1]==0)
                vars[d1] = 1; // r u2d
            else if( vars[ox]==width-bs && vars[d1]==1)
                vars[d1] = 2; // r2l

        }

        // Because this value will be changed so
        // all the threads should set their registers before
        // they move forward
        int ox_d = vars[ox];
        int oy_d = vars[oy];

        // Just putting it here so that all the threads should have set their
        // values before moving on, as this value will be changed
        __syncthreads();

        if(vars[d1]==0)
        {

            if(i == 0 && j < bs)
            {
                int index = j*width + ox_d + oy_d*width;
                int index2 = j*width + ox_d + oy_d*width +bs;

                atomicSub(&ary_shared[in[index]],1);
                atomicAdd(&ary_shared[in[index2]],1);
            }

            // The first thread of the first block should set this value
            if(k==0)
                vars[ox]++;
        }
        else if(vars[d1]==1||vars[d1]==3)
        {

            if(i == 0 && j < bs)
            {
                /*if(j==0)
                printf("Entered 1||3\n");*/
                int index = j*width + ox_d + oy_d*width;
                int index2 = j*width + ox_d + (oy_d+bs)*width;

                atomicSub(&ary_shared[in[index]],1);
                atomicAdd(&ary_shared[in[index2]],1);

            }
            // The first thread of the first block should set this value
            if(k==0)
                vars[oy]++;

        }
        else if(vars[d1]==2)
        {

            if(i == 0 && j < bs)
            {
                int index = j*width + ox_d-1 + oy_d*width;
                int index2 = j*width + ox_d-1 + oy_d*width +bs;

                atomicAdd(&ary_shared[in[index]],1);
                atomicSub(&ary_shared[in[index2]],1);

            }
            // The first thread of the first block should set this value
            if(k==0 )
                vars[ox]--;
         }
        __syncthreads();

        //ary_shared has been calculated

        // Reset the hmin and hminc values
        // again the same task as done in the if(n) loop
        if(k==0)
        {
            vars[hmin]=0;
            vars[hminc]=0;
            vars[hmax]=0;
            vars[hmaxc]=0;
        }
        __syncthreads();

        int minth = 1>((bs*bs)/20)? 1: ((bs*bs)/20);
        prefix_sum[k] = ary_shared[k];

        int l;
        for(l=0;l<=limit;l++)
        {
            sum_s[k]=prefix_sum[k];

            if(k >= (int)pow((float)2,(float)l))
            {  
                prefix_sum[k]+=sum_s[k-(int)pow((float)2,(float)l)];
                // Find out the minimum index for which the cummulative sum crosses threshold
                if(prefix_sum[k] > minth)
                {
                    atomicMin(&vars[hmin],k);
                }
            }
            __syncthreads();
        }

         // set the maximum value here
        if(k==0)
        {
            vars[hminc]=prefix_sum[255];
            vars[hmin]--;
        }
        __syncthreads();

        // Calculate maxth
        int maxth = 1>((bs*bs)/20)? 1: ((bs*bs)/20);
        prefix_sum[k] = ary_shared[255-k];

        for(l=0;l<=limit;l++)
        {
            sum_s[k]=prefix_sum[k];

            if(k >= (int)pow((float)2,(float)l))
            {  
                prefix_sum[k]+=sum_s[k-(int)pow((float)2,(float)l)];
                // Find out the minimum index for which the cummulative sum crosses threshold
                if(prefix_sum[k] > maxth)
                {
                    atomicMin(&vars[hmax], k);
                }
            }
             __syncthreads();
        }
         // set the maximum value here

         if(k==0)
         {
            vars[hmaxc]=prefix_sum[255];
            vars[hmax]--;
            vars[hmax]=255-vars[hmax];
         }
        __syncthreads();

        int rng = vars[hmax] - vars[hmin];
        if(rng >= vars[cut])
        {
          if( k <= vars[hmin] )
                he_shared[k] = 0;
          else if( k >= vars[hmax])
                he_shared[k] = 255;
          else
                he_shared[k] = (255 * (k - vars[hmin])) / rng;
        }
         __syncthreads();


        if(i>0 && i<=bs && j>0 && j<=bs)
        {
           int base = (vars[oy]*width+vars[ox])+ (i-1)*width + (j-1);

           if(rng >= vars[cut])
           {
              int value = he_shared[in[base]];
              buf[base]+=value;
              lim[base]++;
           }
           else
           {

              buf[base]+=255;
              lim[base]++;

           }
        }

        // This just might cause a little bit of problem
        if(k==0)
            vars[n]--;

        // All threads will wait here before continuing the while loop
        __syncthreads();

    }// end of while(n)
}

1 Ответ

3 голосов
/ 10 октября 2011

Во-первых, вам нужно -arch sm_12 (или в вашем случае это действительно должно быть -arch sm_13), чтобы включить атомарные операции.

Что касается производительности, то нет никаких гарантий, что ваше ядро ​​будет работать быстрее, чем обычнокод на процессоре - есть много проблем, которые действительно не вписываются в модель CUDA, и они действительно могут работать намного медленнее, чем на процессоре.Вам необходимо провести некоторый анализ / проектирование / моделирование перед кодированием любых ядер CUDA, чтобы не тратить много времени на то, что никогда не будет летать.

Сказав это, может быть способом более эффективной реализации вашего алгоритма - может быть, вы могли бы опубликовать код процессора и затем предложить идеи относительно того, как эффективно реализовать его в CUDA?

...