Параллельное сокращение в CUDA для вычисления простых чисел - PullRequest
2 голосов
/ 26 сентября 2011

У меня есть код для вычисления простых чисел, которые я распараллелил с использованием OpenMP:

    #pragma omp parallel for private(i,j) reduction(+:pcount) schedule(dynamic)
    for (i = sqrt_limit+1; i < limit; i++)
    {
            check = 1;
            for (j = 2; j <= sqrt_limit; j++)
            {

                    if ( !(j&1) && (i&(j-1)) == 0 )
                    {
                            check = 0;
                            break;
                    }

                    if ( j&1 && i%j == 0 )
                    {
                            check = 0;
                            break;
                    }

            }
            if (check)
                pcount++;

    }

Я пытаюсь перенести его на графический процессор, и я хотел бы уменьшить количество, как я сделал для примера OpenMPвыше.Ниже приведен мой код, который кроме предоставления неверных результатов также медленнее:

__global__ void sieve ( int *flags, int *o_flags, long int sqrootN, long int N) 
{
    long int gid = blockIdx.x*blockDim.x+threadIdx.x, tid = threadIdx.x, j;
    __shared__ int s_flags[NTHREADS];

    if (gid > sqrootN && gid < N)
            s_flags[tid] = flags[gid];
    else
            return;
    __syncthreads();

    s_flags[tid] = 1;

    for (j = 2; j <= sqrootN; j++)
    {
            if ( gid%j == 0 )
            {
                    s_flags[tid] = 0;
                    break;
            }
    }
    //reduce        
    for(unsigned int s=1; s < blockDim.x; s*=2)
    {
            if( tid % (2*s) == 0 )
            {
                    s_flags[tid] += s_flags[tid + s];
            }
            __syncthreads();
    }
    //write results of this block to the global memory
    if (tid == 0)
            o_flags[blockIdx.x] = s_flags[0];

}

Прежде всего, как мне сделать это ядро ​​быстрым, я думаю, что узким местом является цикл for, и я не уверенкак это заменить.И затем, мои подсчеты не верны.Я изменил оператор '%' и заметил некоторое преимущество.

В массиве flags я пометил простые числа от 2 до sqroot (N), в этом ядре я вычисляю простые числа из sqroot (N) к N, но мне нужно проверить, делится ли каждое число в {sqroot (N), N} на простые числа в {2, sqroot (N)}.В массиве o_flags хранятся частичные суммы для каждого блока.


РЕДАКТИРОВАТЬ: Следуя предложению, я изменил свой код (теперь я лучше понимаю, что такое комментарий к syncthreads);Я понял, что мне не нужен массив флагов, и в моем случае работают только глобальные индексы.На данный момент меня беспокоит медлительность кода (больше, чем корректность), которую можно отнести к циклу for.Кроме того, после определенного размера данных (100000) ядро ​​выдавало неверные результаты для последующих размеров данных.Даже для данных размером менее 100000 результаты сокращения графического процессора неверны (участник форума NVidia отметил, что это может быть из-за того, что мой размер данных не равен степени 2).Итак, есть еще три (может быть связанных) вопроса -

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

  2. Почему он дает правильные результаты только для определенных размеров данных?

  3. Как я могу изменить сокращение?

    __global__ void sieve ( int *o_flags, long int sqrootN, long int N )
    {
    unsigned int gid = blockIdx.x*blockDim.x+threadIdx.x, tid = threadIdx.x;
    volatile __shared__ int s_flags[NTHREADS];
    
    s_flags[tid] = 1;
    for (unsigned int j=2; j<=sqrootN; j++)
    {
           if ( gid % j == 0 )
                s_flags[tid] = 0;
    }
    
    __syncthreads();
    //reduce
    reduce(s_flags, tid, o_flags);
    }
    

1 Ответ

3 голосов
/ 26 сентября 2011

Хотя я и признаю, что ничего не знаю о просеивании простых чисел, в вашей версии графического процессора есть множество проблем с корректностью, которые не позволяют ему работать правильно, независимо от того, корректен ли реализуемый вами алгоритм:

  1. __syncthreads() звонки должны быть безусловными.Неправильно писать код, в котором из-за расхождения ветвей некоторые потоки в одной и той же деформации могут не выполнить вызов __syncthreads().Основной PTX - bar.sync, и руководство PTX говорит следующее:

    Барьеры выполняются для каждого деформации, как если бы все потоки в деформации были активными.Таким образом, если какой-либо поток в деформации выполняет команду bar, то все потоки в деформации выполнили инструкцию bar.Все нити в основе останавливаются до тех пор, пока барьер не завершится, и счетчик прибытия для барьера увеличивается на размер основы (не количество активных нитей в основе).В условно исполняемом коде инструкция bar должна использоваться только в том случае, если известно, что все потоки одинаково оценивают условие (деформация не расходится).Поскольку барьеры выполняются для каждой деформации, необязательный счетчик потоков должен быть кратным размеру деформации.

  2. Ваш код безоговорочно устанавливает s_flags в единицу после условной загрузки некоторых значений из глобальной памяти.Конечно, это не может быть целью кода?
  3. В коде отсутствует барьер синхронизации между просеивающим кодом и сокращением, это может привести к гонке совместно используемой памяти и неверным результатам от сокращения.
  4. Если вы планируете запускать этот код на карте класса Fermi, массив разделяемой памяти должен быть объявлен volatile, чтобы предотвратить возможность оптимизации оптимизации компилятором сокращения общей памяти.

Если вы исправите этивещи, код может работать.Производительность - это совершенно другая проблема.Конечно, на старом оборудовании целочисленная операция по модулю была очень, очень медленной и не рекомендована.Я могу вспомнить, читая некоторые материалы, предполагающие, что Sieve of Atkin был полезным подходом к быстрой генерации простых чисел на графических процессорах.

...