CUDA распараллеливание вложенного цикла for

Я новичок в CUDA. Я пытаюсь распараллелить следующий код. Прямо сейчас он сидит на ядре, но не использует потоки вообще, таким образом, медленно. Я пытался использовать это ответ но пока безрезультатно.

предполагается, что ядро генерирует первые n простых чисел, помещает их в массив device_primes, и этот массив позже доступен с хоста. Код правильный и отлично работает в серийной версии, но мне нужно ускорить его, возможно, с использованием общей памяти.

//CUDA kernel code
__global__ void generatePrimes(int* device_primes, int n) 
{
//int i = blockIdx.x * blockDim.x + threadIdx.x;
//int j = blockIdx.y * blockDim.y + threadIdx.y;

int counter = 0;
int c = 0;

for (int num = 2; counter < n; num++)
{       
    for (c = 2; c <= num - 1; c++)
    { 
        if (num % c == 0) //not prime
        {
            break;
        }
    }
    if (c == num) //prime
    {
        device_primes[counter] = num;
        counter++;
    }
}
}

моя текущая, предварительная и определенно неправильная попытка распараллелить это выглядит следующим образом:

//CUDA kernel code
__global__ void generatePrimes(int* device_primes, int n) 
{
int i = blockIdx.x * blockDim.x + threadIdx.x;
int j = blockIdx.y * blockDim.y + threadIdx.y;
int num = i + 2; 
int c = j + 2;
int counter = 0;

if ((counter >= n) || (c > num - 1))
{
    return;
}
if (num % c == 0) //not prime
{

}
if (c == num) //prime
{
    device_primes[counter] = num;
    counter++;
}
num++;
c++;
}

но этот код заполняет массив данными, которые не имеют смысла. Кроме того, многие значения являются нулями. Заранее спасибо за любую помощь, это ценится.

1 ответов


у вас есть некоторые проблемы в коде, например:

int num = i + 2;

предоставление потоку 0 взаимодействия 2, потоку 1 итерации 3 и так далее. Проблема в том, что следующая итерация, которую будут вычислять потоки, основана на num++;. Таким образом, это означает, что поток 0 выполнит следующую итерацию 3, уже выполненную потоком 1. Поэтому у вас будут избыточные вычисления. Кроме того,я думаю, что для этой проблемы будет проще использовать только одно измерение вместо 2 (x, y). Таким образом, основываясь в этом предположении вы должны изменить num++ для:

num += blockDim.x * gridDim.x;

другая проблема заключается в том, что вы не приняли во внимание, что счетчик переменных должен быть разделен между потоками. В противном случае каждый поток попытается найти " n " простых чисел, и все они будут заполнять весь массив. Поэтому вам нужно изменить int counter = 0; для общей или глобальной переменной мы будем использовать глобальную переменную, чтобы она была видна среди всех потоков из всех блоков. Мы можем использовать нулевую позицию массива device_primes держать счетчик.

плюс, вы должны инициализировать это значение, вы дадите это задание только одному потоку. Давайте дадим это задание потоку с id = 0, так что:

if (thread_id == 0) device_primes[0] = 1;

но поскольку эта переменная является глобальной и будет записываться всеми потоками, вы должны гарантировать, что все потоки, прежде чем писать на ней, увидят, что счетчик равен 1 (первая позиция device_primes с простыми числами, ноль для счетчика), поэтому вам нужно добавить также барьер в конце, Итак:

if (thread_id == 0) device_primes[0] = 1;
__syncthreads()

таким образом, возможное решение (неэффективное):

__global__ void getPrimes(int *device_primes,int n)
{ 
    int c = 0;
    int thread_id = blockIdx.x * blockDim.x + threadIdx.x;
    int num = thread_id;

    if (thread_id == 0) device_primes[0] = 1;
    __syncthreads();

    while(device_primes[0] < n)
    {

        for (c = 2; c <= num - 1; c++)
        { 
            if (num % c == 0) //not prime
            {
                break;
            }
        }

        if (c == num) //prime
        {
            int pos = atomicAdd(&device_primes[0],1);
            device_primes[pos] = num;

        }

        num += blockDim.x * gridDim.x; // Next number for this thread       
    }
}

следующая строка atomicAdd (&device_primes[0], 1); будет в основном делать device_primes[0]++; но поскольку counter является глобальным, вы должны гарантировать взаимное исключение. Вот почему я использовал эту атомную операцию. Обратите внимание, что вам может потребоваться компиляция с флагом-arch sm_20.

оптимизация: С точки зрения кода предпочтительнее подход с меньшей синхронизацией/без синхронизации. Также вы можете уменьшить количество вычислений, принимая во внимание некоторые из приличий простых чисел, как вы можете видеть вhttp://en.wikipedia.org/wiki/Sieve_of_Eratosthenes.