Ваш код на 100% правильный.Проблема в том, что ваши битовые сдвиги не учитывают последнюю часть вашего массива.Вы можете легко исправить это, искусственно расширив массив до следующей степени 2. Таким образом, весь ваш массив будет уменьшен, а лишние «элементы» (они на самом деле не существуют) просто игнорируются.
#include <math.h>
__global__ void reduction(float *g_data, int n){
// figure out exponent of next larger power of 2
int exponent = ceilf(log2f(n));
// calculate next larger power of 2
int size = (int)powf(2, exponent);
__shared__ float partialSum[NUM_ELEMENTS];
int tx = threadIdx.x;
int i = tx + blockIdx.x * blockDim.x;
if (i < n){
partialSum[tx] = g_data[i];
}
for (int stride = size / 2; stride > 0; stride >>= 1){
__syncthreads();
if (tx < stride) {
// all threads that run out of bounds do nothing
// equivalent to adding 0
if((tx + stride) < n)
partialSum[tx] += partialSum[tx + stride];
}
}
if (tx == 0){
g_data[blockIdx.x] = partialSum[tx];
}
}
Редактировать
Что касается вашего комментария, этот метод сокращения никогда не будет работать для массива, который сокращается в несколько блоков.Таким образом, для вычислительных возможностей 1.0-1.3 самый большой массив, который вы можете уменьшить, составляет 512 элементов, для вычислительных возможностей> 1.3 вы можете сделать до 1024 элементов, это максимальное количество потоков в блоке.
Это потому, что __shared__
память распределена между потоками, а не блоками .Таким образом, чтобы уменьшить массив, разбросанный по нескольким блокам, вам нужно разделить массив так, чтобы каждый блок уменьшал кусок, а затем использовать память __global__
для уменьшения значений из всех блоков.Однако __global__
память примерно в 10-20 раз медленнее, чем (встроенная) __shared__
память, поэтому, как только вы начнете использовать много блоков, это станет очень неэффективным.
Альтернатива будетчтобы каждый поток обрабатывал несколько индексов, однако, в конечном итоге ваш массив partialSum
больше не помещается в разделяемую память и в любом случае переполняется в глобальную память.Этот подход также означает, что вы никогда не сможете использовать более 512 (или 1024) потоков, что противоречит цели использования CUDA, которая зависит от запуска очень большого количества потоков, чтобы скрыть задержку и сделать дорогостоящую передачу памяти от хоста к устройству стоящей..