可移植性与可读性:网格步长循环代码与原始顺序循环代码更为相似,因此对其他用户来说更加易于理解。事实上,我们可以轻松地编写一个内核版本,它可以在 GPU 上作为并行 CUDA 内核运行,也可以在 CPU 上作为顺序循环运行。 Hemi 库提供了一个 grid_stride_range() 辅助工具,通过 C++11 的基于范围的 for 循环使这一过程变得非常简单。
HEMI_LAUNCHABLE
void saxpy(int n, float a, float *x, float *y)
{
for (auto i : hemi::grid_stride_range(0, n)) {
y[i] = a * x[i] + y[i];
}
}
我们可以使用以下代码启动内核,当编译为 CUDA 时,它将生成一个内核启动,而当编译为 CPU 时,它将生成一个函数调用。
hemi::cudaLaunch(saxpy, 1<<20, 2.0, x, y);
网格步长循环是让你的 CUDA 内核更具灵活性、可扩展性、易调试性,甚至可移植性的极佳方式。尽管本文中的例子都使用了 CUDA C/C++,但同样的概念也适用于其他 CUDA 语言,如 CUDA Fortran。
示例代码
#include <stdio.h>
void init(int *a, int N) {
int i;
for (i = 0; i < N; ++i)
{
a[i] = i;
}
}
__global__ void doubleElements(int *a, int N) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int stride = gridDim.x * blockDim.x;
for (int i = idx; i < N; i += stride) {
a[i] *= 2;
}
}
bool checkElementsAreDoubled(int *a, int N) {
int i;
for (i = 0; i < N; ++i) {
if (a[i] != i*2) return false;
}
return true;
}
int main() {
int N = 10000;
int *a;
size_t size = N * sizeof(int);
cudaMallocManaged(&a, size);
init(a, N);
size_t threads_per_block = 256;
size_t number_of_blocks = 32;
doubleElements<<<number_of_blocks, threads_per_block>>>(a, N);
cudaDeviceSynchronize();
bool areDoubled = checkElementsAreDoubled(a, N);
printf("All elements were doubled? %s\n", areDoubled ? "TRUE" : "FALSE");
cudaFree(a);
return 0;
}