跳到主要内容

核函数详解

核函数(Kernel)是 CUDA 编程的核心,是在 GPU 上并行执行的函数。本章将详细介绍核函数的定义、执行配置和优化技巧。

核函数基础

函数修饰符

CUDA 扩展了 C++ 的函数修饰符:

修饰符执行位置调用位置返回类型
__global__DeviceHost 或 Devicevoid
__device__DeviceDevice任意
__host__HostHost任意

核函数定义

__global__ void kernelName(parameters) {
}

核函数的限制:

  1. 返回类型必须是 void
  2. 不能是类的成员函数(除非使用静态成员)
  3. 不能使用可变参数
  4. 不能使用递归(CUDA 9.0 之前)
  5. 不能使用静态变量

函数组合修饰符

可以同时使用多个修饰符:

__host__ __device__ int add(int a, int b) {
return a + b;
}

这样定义的函数可以同时在 CPU 和 GPU 上调用。

执行配置

基本语法

kernel<<<gridDim, blockDim>>>(args);

dim3 类型

dim3 是 CUDA 定义的三维向量类型:

dim3 v1;
dim3 v2(x);
dim3 v3(x, y);
dim3 v4(x, y, z);

未指定的维度默认为 1。

一维配置

int n = 10000;
int blockSize = 256;
int gridSize = (n + blockSize - 1) / blockSize;

kernel<<<gridSize, blockSize>>>(data, n);

二维配置

int width = 1024, height = 1024;
dim3 blockSize(16, 16);
dim3 gridSize((width + 15) / 16, (height + 15) / 16);

kernel<<<gridSize, blockSize>>>(data, width, height);

三维配置

int nx = 64, ny = 64, nz = 64;
dim3 blockSize(4, 4, 4);
dim3 gridSize((nx + 3) / 4, (ny + 3) / 4, (nz + 3) / 4);

kernel<<<gridSize, blockSize>>>(data, nx, ny, nz);

完整执行配置

kernel<<<gridDim, blockDim, sharedMemSize, stream>>>(args);
参数类型说明
gridDimdim3/intGrid 维度
blockDimdim3/intBlock 维度
sharedMemSizesize_t动态共享内存大小
streamcudaStream_t执行流

内置变量

核函数中可以访问以下内置变量:

线程和块索引

变量类型说明
threadIdxuint3线程在 Block 中的索引
blockIdxuint3Block 在 Grid 中的索引
blockDimdim3Block 的维度
gridDimdim3Grid 的维度

其他内置变量

变量类型说明
warpSizeintWarp 大小(通常为 32)

计算全局索引

__device__ int getGlobalIdx_1D() {
return blockIdx.x * blockDim.x + threadIdx.x;
}

__device__ int getGlobalIdx_2D() {
int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
return y * gridDim.x * blockDim.x + x;
}

__device__ int getGlobalIdx_3D() {
int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
int z = blockIdx.z * blockDim.z + threadIdx.z;
return z * gridDim.x * blockDim.x * gridDim.y * blockDim.y
+ y * gridDim.x * blockDim.x + x;
}

核函数限制

寄存器限制

每个线程可用的寄存器数量有限。过多的寄存器使用会导致:

  1. 占用率下降
  2. 寄存器溢出到局部内存

查看寄存器使用情况:

nvcc -Xptxas -v kernel.cu

参数传递限制

核函数参数通过常量内存传递,总大小限制为 4KB:

__global__ void kernel(float *data, int n) {
}

struct LargeStruct {
float data[10000];
};

__global__ void badKernel(LargeStruct s) {
}

解决方案:使用指针传递大型数据。

递归限制

CUDA 9.0 之前不支持递归,CUDA 9.0+ 支持有限递归:

__global__ void recursiveKernel(int depth) {
if (depth > 0) {
recursiveKernel<<<1, 1>>>(depth - 1);
cudaDeviceSynchronize();
}
}

动态并行

动态并行允许核函数启动其他核函数(CUDA 5.0+)。

基本用法

__global__ void childKernel(int *data, int n) {
int idx = threadIdx.x;
if (idx < n) {
data[idx] *= 2;
}
}

__global__ void parentKernel(int *data, int n) {
if (threadIdx.x == 0) {
childKernel<<<1, n>>>(data, n);
cudaDeviceSynchronize();
}
}

注意事项

  1. 需要链接 CUDA 运行时:nvcc -rdc=true
  2. 子核函数在新的 Grid 中执行
  3. 父核函数必须等待子核函数完成

核函数优化

1. 减少分支分化

避免 Warp 内的分支分化:

__global__ void badBranch(int *data, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
if (idx % 2 == 0) {
data[idx] *= 2;
} else {
data[idx] += 1;
}
}
}

__global__ void goodBranch(int *data, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
int warpIdx = idx % 32;
if (warpIdx < 16) {
data[idx] *= 2;
} else {
data[idx] += 1;
}
}
}

2. 循环展开

使用 #pragma unroll 展开循环:

__global__ void kernel(float *data) {
int idx = threadIdx.x;
float sum = 0.0f;

#pragma unroll
for (int i = 0; i < 32; i++) {
sum += data[idx + i * blockDim.x];
}

data[idx] = sum;
}

3. 使用 restrict

使用 __restrict__ 告诉编译器指针不重叠:

__global__ void kernel(float * __restrict__ a, 
float * __restrict__ b,
float * __restrict__ c, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
c[idx] = a[idx] + b[idx];
}
}

4. 使用 const 和 restrict

__global__ void kernel(const float * __restrict__ input,
float * __restrict__ output, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
output[idx] = input[idx] * 2.0f;
}
}

核函数模板

CUDA 支持模板核函数:

template<typename T>
__global__ void addKernel(T *a, T *b, T *c, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
c[idx] = a[idx] + b[idx];
}
}

int main() {
addKernel<float><<<grid, block>>>(d_a, d_b, d_c, n);
addKernel<int><<<grid, block>>>(d_a, d_b, d_c, n);
}

核函数类

CUDA 支持使用函数对象(Functor):

struct AddOp {
__device__ float operator()(float a, float b) {
return a + b;
}
};

struct MulOp {
__device__ float operator()(float a, float b) {
return a * b;
}
};

template<typename Op>
__global__ void binaryOp(float *a, float *b, float *c, int n, Op op) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
c[idx] = op(a[idx], b[idx]);
}
}

int main() {
binaryOp<<<grid, block>>>(d_a, d_b, d_c, n, AddOp());
binaryOp<<<grid, block>>>(d_a, d_b, d_c, n, MulOp());
}

错误处理

启动错误

kernel<<<grid, block>>>(args);

cudaError_t err = cudaGetLastError();
if (err != cudaSuccess) {
printf("Launch error: %s\n", cudaGetErrorString(err));
}

执行错误

kernel<<<grid, block>>>(args);

cudaError_t err = cudaDeviceSynchronize();
if (err != cudaSuccess) {
printf("Execution error: %s\n", cudaGetErrorString(err));
}

完整错误检查

#define CUDA_CHECK(call) \
do { \
cudaError_t err = call; \
if (err != cudaSuccess) { \
fprintf(stderr, "CUDA error at %s:%d: %s\n", \
__FILE__, __LINE__, cudaGetErrorString(err)); \
exit(EXIT_FAILURE); \
} \
} while(0)

#define KERNEL_CHECK(call) \
do { \
call; \
cudaError_t err = cudaGetLastError(); \
if (err != cudaSuccess) { \
fprintf(stderr, "Kernel launch error at %s:%d: %s\n", \
__FILE__, __LINE__, cudaGetErrorString(err)); \
exit(EXIT_FAILURE); \
} \
err = cudaDeviceSynchronize(); \
if (err != cudaSuccess) { \
fprintf(stderr, "Kernel execution error at %s:%d: %s\n", \
__FILE__, __LINE__, cudaGetErrorString(err)); \
exit(EXIT_FAILURE); \
} \
} while(0)

KERNEL_CHECK(kernel<<<grid, block>>>(args));

小结

本章详细介绍了 CUDA 核函数:

  1. 函数修饰符__global____device____host__
  2. 执行配置:Grid 和 Block 维度的设置
  3. 内置变量threadIdxblockIdxblockDimgridDim
  4. 核函数限制:返回类型、参数大小、递归等
  5. 动态并行:核函数启动核函数
  6. 优化技巧:减少分支分化、循环展开、使用 __restrict__

下一章将介绍 同步机制,学习如何在 CUDA 程序中进行线程同步和原子操作。