Skip to content

Commit 60afcc9

Browse files
authored
Merge pull request #7 from SimeonEhrig/example_reduce
2 parents 211d8f5 + 2b6f83e commit 60afcc9

File tree

2 files changed

+195
-0
lines changed

2 files changed

+195
-0
lines changed

examples/basic/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,14 @@ target_sources(${_TARGET_RANDOM_2D_MATRIX}
3232
set_target_properties(${_TARGET_RANDOM_2D_MATRIX} PROPERTIES
3333
CUDA_CXX_STANDARD 17
3434
)
35+
36+
set(_TARGET_REDUCE reduce)
37+
38+
add_executable(${_TARGET_REDUCE})
39+
target_sources(${_TARGET_REDUCE}
40+
PRIVATE
41+
reduce.cu)
42+
set_target_properties(${_TARGET_REDUCE} PROPERTIES
43+
CXX_STANDARD 17
44+
CUDA_CXX_STANDARD 17
45+
)

examples/basic/reduce.cu

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
#include <iostream>
2+
#include <random>
3+
#include <vector>
4+
#include <algorithm>
5+
6+
// The wrapper macro is required, that __LINE__ is correct pointing to the line, where the check fails
7+
#define checkCudaError(ans) \
8+
{ \
9+
checkCudaErrorFunc((ans), __FILE__, __LINE__); \
10+
}
11+
12+
inline void checkCudaErrorFunc(cudaError_t err, const char *file, int line)
13+
{
14+
if (err != cudaSuccess)
15+
{
16+
std::cout << "\r" << file << ":" << line << " -> Cuda Error " << err << ": " << cudaGetErrorString(err) << std::endl;
17+
std::cout << "Aborting..." << std::endl;
18+
exit(0);
19+
}
20+
}
21+
22+
// The reduction algorithms divide all elements in logical blocks with the size of threads.
23+
// Each local block is reduced to a single element.
24+
// A grid stride loop maps the logical blocks to cuda blocks (both has the same size).
25+
// The output array has the size of the number of logical blocks.
26+
// Uses only global memory.
27+
__global__ void reduce_gm(unsigned int const size, unsigned int *const input, unsigned int *const output)
28+
{
29+
int const id = threadIdx.x + blockIdx.x * blockDim.x;
30+
int const stride = blockDim.x * gridDim.x;
31+
// use grid stride loop to distribute the logical blocks to cuda blocks.
32+
for (int block_offset_id = id, virtual_block_id = blockIdx.x; block_offset_id < size; block_offset_id += stride, virtual_block_id += gridDim.x)
33+
{
34+
// reduce all elements of logical block to a single element.
35+
for (int max_threads_blocks = blockDim.x / 2; max_threads_blocks > 0; max_threads_blocks /= 2)
36+
{
37+
if (threadIdx.x < max_threads_blocks)
38+
{
39+
input[block_offset_id] += input[block_offset_id + max_threads_blocks];
40+
}
41+
__syncthreads();
42+
}
43+
if (threadIdx.x == 0)
44+
{
45+
// write single element to output
46+
output[virtual_block_id] = input[block_offset_id];
47+
}
48+
__syncthreads();
49+
}
50+
}
51+
52+
// The reduction algorithms divide all elements in logical blocks with the size of threads.
53+
// Each local block is reduced to a single element.
54+
// A grid stride loop maps the logical blocks to cuda blocks (both has the same size).
55+
// The output array has the size of the number of logical blocks.
56+
// Uses shared memory to speed up.
57+
template <auto LOGICAL_BLOCK_SIZE>
58+
__global__ void reduce_sm(unsigned int const size, unsigned int const upper_bound_size, unsigned int *const input, unsigned int *const output)
59+
{
60+
__shared__ unsigned int reduction_memory[LOGICAL_BLOCK_SIZE];
61+
62+
int const id = threadIdx.x + blockIdx.x * blockDim.x;
63+
int const stride = blockDim.x * gridDim.x;
64+
// use grid stride loop to distribute the logical blocks to cuda blocks.
65+
for (int block_offset_id = id, virtual_block_id = blockIdx.x; block_offset_id < upper_bound_size; block_offset_id += stride, virtual_block_id += gridDim.x)
66+
{
67+
if (block_offset_id < size)
68+
{
69+
reduction_memory[threadIdx.x] = input[block_offset_id];
70+
}
71+
else
72+
{
73+
reduction_memory[threadIdx.x] = 0;
74+
}
75+
// reduce all elements of logical block to a single element.
76+
__syncthreads();
77+
for (int max_threads_blocks = blockDim.x / 2; max_threads_blocks > 0; max_threads_blocks /= 2)
78+
{
79+
if (threadIdx.x < max_threads_blocks)
80+
{
81+
reduction_memory[threadIdx.x] += reduction_memory[threadIdx.x + max_threads_blocks];
82+
}
83+
__syncthreads();
84+
}
85+
86+
if (threadIdx.x == 0)
87+
{
88+
// write single element to output
89+
output[virtual_block_id] = reduction_memory[0];
90+
}
91+
}
92+
}
93+
94+
// Helper function -> should be replaced by html visualization ;-)
95+
template <typename T>
96+
void print_vec(std::vector<T> vec)
97+
{
98+
for (auto const v : vec)
99+
{
100+
std::cout << v << " ";
101+
}
102+
std::cout << std::endl;
103+
}
104+
105+
int main(int argc, char **argv)
106+
{
107+
int constexpr blocks = 10;
108+
int constexpr threads = 32;
109+
110+
// number of input elements
111+
unsigned int const size = 1632;
112+
size_t const data_size_byte = sizeof(unsigned int) * size;
113+
114+
// number of logical blocks
115+
size_t output_elements = size / threads;
116+
// add an extra element, if logical blocks does not fit in cuda blocks
117+
output_elements += (size % threads == 0) ? 0 : 1;
118+
size_t const output_size_byte = sizeof(unsigned int) * output_elements;
119+
120+
std::vector<unsigned int> h_data(size);
121+
std::vector<unsigned int> h_output(output_elements, 0);
122+
123+
// initialize data matrix with random numbers betweem 0 and 10
124+
std::uniform_int_distribution<unsigned int> distribution(
125+
0,
126+
10);
127+
std::default_random_engine generator;
128+
std::generate(
129+
h_data.begin(),
130+
h_data.end(),
131+
[&distribution, &generator]()
132+
{ return distribution(generator); });
133+
134+
// calculate result for verification
135+
unsigned int const expected_result = std::reduce(h_data.begin(), h_data.end());
136+
137+
unsigned int *d_data = nullptr;
138+
unsigned int *d_output = nullptr;
139+
140+
checkCudaError(cudaMalloc((void **)&d_data, data_size_byte));
141+
checkCudaError(cudaMalloc((void **)&d_output, output_size_byte));
142+
checkCudaError(cudaMemcpy(d_data, h_data.data(), data_size_byte, cudaMemcpyHostToDevice));
143+
144+
bool const sm_version = false;
145+
146+
if (!sm_version)
147+
{
148+
if (size % threads)
149+
{
150+
std::cerr << "size needs to be multiple of number of threads" << std::endl;
151+
exit(1);
152+
}
153+
reduce_gm<<<blocks, threads>>>(size, d_data, d_output);
154+
}
155+
else
156+
{
157+
size_t const upper_bound_size = output_elements * threads;
158+
reduce_sm<threads><<<blocks, threads>>>(size, upper_bound_size, d_data, d_output);
159+
}
160+
checkCudaError(cudaGetLastError());
161+
162+
checkCudaError(cudaMemcpy(h_output.data(), d_output, output_size_byte, cudaMemcpyDeviceToHost));
163+
164+
unsigned int sum = 0;
165+
166+
// Reduce all sums of the logical blocks on CPU.
167+
// Otherwise a second kernel or cuda cooperative groups are required to performe block synchronization.
168+
for (unsigned int const v : h_output)
169+
{
170+
sum += v;
171+
}
172+
173+
if (sum == expected_result)
174+
{
175+
std::cout << "reduction kernel works correctly" << std::endl;
176+
}
177+
else
178+
{
179+
std::cout << "sum: " << sum << std::endl;
180+
std::cout << "expected result: " << expected_result << std::endl;
181+
}
182+
183+
return 0;
184+
}

0 commit comments

Comments
 (0)