CUDA(Compute Unified Device Architecture)是由NVIDIA推出的并行计算平台和应用程序接口模型。它允许开发者利用NVIDIA GPU的强大计算能力来加速通用计算任务,而不仅仅是图形渲染。通过CUDA,开发者可以编写C、C++或Fortran代码,并将其扩展以在GPU上运行,从而显著提高性能,特别是在处理大规模数据集和复杂算法时。
传统的C/C++编程主要依赖于CPU进行串行执行,即一个指令接一个指令地顺序执行。然而,CUDA编程的核心在于数据并行性和任务并行性。这意味着大量相似的操作可以同时应用于不同的数据元素,或者多个独立的任务可以并行执行。这种并行性特别适合处理大规模的数据集,如图像处理、科学计算等。
CUDA采用的是**单指令多线程(SIMT)**架构,不同于传统的多核CPU上的多线程(SMP)。在SIMT中,一组线程(通常称为warp)会同时执行相同的指令,但作用于不同的数据。这种方式非常适合数据并行的任务,例如矩阵乘法、图像滤波等。
cudaMalloc
和cudaFree
),并且要考虑到主机与设备之间的数据传输成本。__syncthreads()
确保它们在继续执行之前完成某些关键步骤。cudaSetDevice
选择要使用的GPU设备。cudaMalloc
为设备端变量分配内存,使用cudaMemcpy
将数据从主机复制到设备。__global__
关键字声明核函数,指定输入输出参数和执行逻辑。<<<...>>>
语法配置网格和块尺寸,并启动核函数。例如,kernel<<>>(args)
。cudaDeviceSynchronize
等待所有先前启动的内核执行完毕,确保结果可用。cudaMemcpy
将计算结果从设备复制回主机。cudaFree
释放设备端分配的内存,调用cudaDeviceReset
重置设备状态。CUDA编程模型基于一个分层的线程组织结构:
这种层次化的结构允许程序员灵活地根据问题规模调整并行度。
CUDA提供多种类型的内存,每种都有其特定用途和访问速度:
核函数是在GPU上执行的函数,用__global__
关键字声明。它们不能直接调用,而是需要从主机代码中启动,语法为kernel<<
。核函数没有返回值,参数列表可以包括输入输出指针以及尺寸信息等。
CUDA采用的是**单指令多线程(SIMT)**架构,在这种架构下,一组线程(通常称为warp)会同时执行相同的指令,但作用于不同的数据。这种方式非常适合数据并行的任务,如矩阵运算、图像处理等。
为了使每个线程知道它应该处理的数据位置,CUDA提供了几个内置变量:
blockIdx
:当前线程块在整个网格中的索引。threadIdx
:当前线程在其所属块内的索引。blockDim
:当前块的维度大小。gridDim
:整个网格的维度大小。通过组合这些变量,我们可以计算出每个线程的唯一ID,进而确定该线程应处理的数据位置。
在同一块内的线程可以通过调用__syncthreads()
函数实现同步,确保所有线程到达这一点后继续执行。这在需要保证块内线程之间的协调时非常有用,比如在共享内存中读写数据之前。
CUDA架构是NVIDIA GPU的并行计算基础,其核心组件包括硬件和软件两部分,共同支撑高性能并行计算。
SM是GPU的核心计算单元,每个SM包含多个CUDA核心(CUDA Cores),负责执行线程。SM的架构设计直接影响CUDA程序的性能。以下是SM的关键特性:
CUDA的内存层次结构分为多层,每层的访问速度和容量不同,开发者需根据需求合理使用:
内存类型 | 访问速度 | 容量 | 用途 |
---|---|---|---|
寄存器(Registers) | 极快 | 线程私有 | 临时变量,访问延迟最低,需合理分配以避免溢出。 |
共享内存(Shared Memory) | 快 | 块内共享 | 块内线程协作,减少全局内存访问(如矩阵乘法中的Tile方法)。 |
L1/L2缓存 | 快 | 小型缓存 | 加速对全局内存的访问,L1缓存位于SM内,L2缓存为全局共享。 |
全局内存(Global Memory) | 较慢 | 大容量 | 存储所有线程可访问的数据,需通过优化访问模式(如内存合并)提升带宽。 |
常量内存(Constant Memory) | 快 | 有限 | 存储只读数据,有独立缓存机制,适合共享不变的数据(如算法参数)。 |
纹理内存(Texture Memory) | 快 | 有限 | 优化空间局部性访问(如图像处理),支持硬件缓存和过滤。 |
关键优化策略:
i
访问A[i]
时,若线程ID按顺序排列,访问会被合并。CUDA的线程模型基于分层结构,开发者需明确线程、块、网格的组织方式,以最大化并行性。
CUDA程序的线程分为三个层次:
线程ID的计算:
int tid = blockIdx.x * blockDim.x + threadIdx.x; // 一维线程ID
int tid = (blockIdx.y * gridDim.x + blockIdx.x) * (blockDim.x * blockDim.y) +
threadIdx.y * blockDim.x + threadIdx.x; // 二维线程ID
CUDA采用**单指令多线程(SIMT)**架构,其核心思想是:
if-else
),会串行化执行所有分支,导致性能下降(称为Warp Divergence)。需尽量减少分支或确保线程路径一致。__syncthreads()
确保所有线程到达该点后再继续执行。例如:__global__ void kernel() {
sharedMem[threadIdx.x] = computeValue();
__syncthreads(); // 确保所有线程完成写入后再读取
result[threadIdx.x] = computeWithSharedMem();
}
块间通信:通过全局内存或原子操作(如atomicAdd
)实现。例如:
__global__ void reduce(int* input, int* output) {
int sum = 0;
for (int i = threadIdx.x; i < N; i += blockDim.x)
sum += input[i];
atomicAdd(output, sum); // 块间累加结果
}
CUDA编程的核心流程分为以下步骤,需结合硬件架构优化:
核函数是CUDA程序的核心,需遵循以下原则:
主机与设备内存分配:
// 主机内存(Host)
float* h_data = (float*)malloc(N * sizeof(float));
// 设备内存(Device)
float* d_data;
cudaMalloc(&d_data, N * sizeof(float));
cudaMemcpyAsync
)与**流(Stream)**并行执行计算和传输。cudaHostAlloc
)减少CPU-GPU传输延迟。dim3 blockSize(256, 1);
dim3 gridSize((N + blockSize.x - 1) / blockSize.x, 1);
kernel<<>>(d_data);
多维计算:对于二维数据(如图像),可使用二维块和网格:
dim3 blockSize(16, 16);
dim3 gridSize((width + 15)/16, (height + 15)/16);
kernel<<>>(d_image);
CUDA的内存层次结构是性能优化的核心,需结合不同内存类型的特点设计代码:
全局内存(Global Memory)
i
访问A[i]
时,若线程ID连续,访问会被合并为一个请求。__global__ void vectorAdd(float* A, float* B, float* C, int N) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < N) C[i] = A[i] + B[i]; // 合并访问:线程i访问连续地址
}
__align__
或cudaMalloc
自动对齐)。共享内存(Shared Memory)
__global__ void matrixMulShared(float* A, float* B, float* C, int N) {
__shared__ float tileA[TILE_WIDTH][TILE_WIDTH];
__shared__ float tileB[TILE_WIDTH][TILE_WIDTH];
// 加载数据到共享内存,后续计算基于共享内存
...
}
纹理内存(Texture Memory)
// 绑定数据到纹理对象
cudaArray* cuArray;
cudaMallocArray(&cuArray, &channelDesc, width, height);
cudaMemcpyToArray(cuArray, 0, 0, h_data, size, cudaMemcpyHostToDevice);
cudaBindTextureToArray(texRef, cuArray, channelDesc);
// 核函数中访问纹理内存
__global__ void textureKernel(...) {
float value = tex2D(texRef, x, y); // 纹理拾取
...
}
常量内存(Constant Memory)
__constant__ float constData[1024]; // 设备端常量内存
cudaMemcpyToSymbol(constData, h_constData, size); // 主机到设备
线程索引与ID计算
一维线程int tid = blockIdx.x * blockDim.x + threadIdx.x;
int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
并行规模与SM资源限制
算术强度(Arithmetic Intensity)
核函数设计原则
流的定义与作用
2. 流的使用示例
// 创建三个流
cudaStream_t stream0, stream1, stream2;
cudaStreamCreate(&stream0);
cudaStreamCreate(&stream1);
cudaStreamCreate(&stream2);
// 异步数据传输与计算
cudaMemcpyAsync(d_A, h_A, size, cudaMemcpyHostToDevice, stream0);
kernel<<>>(d_A, d_B);
cudaMemcpyAsync(h_C, d_C, size, cudaMemcpyDeviceToHost, stream2);
// 等待所有流完成
cudaStreamSynchronize(stream0);
cudaStreamSynchronize(stream1);
cudaStreamSynchronize(stream2);
__global__ void vectorAdd(float* A, float* B, float* C, int N) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < N) C[i] = A[i] + B[i];
}
// 主机代码
int main() {
int N = 1<<20; // 1M元素
float* d_A, *d_B, *d_C;
cudaMalloc(&d_A, N * sizeof(float));
// ... 初始化数据 ...
int blockSize = 256;
int gridSize = (N + blockSize - 1) / blockSize;
vectorAdd<<>>(d_A, d_B, d_C, N);
cudaDeviceSynchronize();
// ... 回收资源 ...
}
#define TILE_WIDTH 16
__global__ void matrixMulShared(float* A, float* B, float* C, int N) {
__shared__ float s_A[TILE_WIDTH][TILE_WIDTH];
__shared__ float s_B[TILE_WIDTH][TILE_WIDTH];
int bx = blockIdx.x, by = blockIdx.y;
int tx = threadIdx.x, ty = threadIdx.y;
int Row = by * TILE_WIDTH + ty;
int Col = bx * TILE_WIDTH + tx;
float sum = 0;
for (int m = 0; m < (N + TILE_WIDTH - 1)/TILE_WIDTH; m++) {
// 加载数据到共享内存
if (Row < N && (m*TILE_WIDTH + tx) < N)
s_A[ty][tx] = A[Row * N + m*TILE_WIDTH + tx];
else s_A[ty][tx] = 0;
if ((m*TILE_WIDTH + ty) < N && Col < N)
s_B[ty][tx] = B[(m*TILE_WIDTH + ty)*N + Col];
else s_B[ty][tx] = 0;
__syncthreads();
// 计算部分内积
for (int k = 0; k < TILE_WIDTH; k++)
sum += s_A[ty][k] * s_B[k][tx];
__syncthreads();
}
if (Row < N && Col < N)
C[Row * N + Col] = sum;
}
texture texRef; // 定义纹理对象
__global__ void blurKernel(float* out, int width, int height) {
int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
if (x < width && y < height) {
float sum = 0;
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
sum += tex2D(texRef, x+dx, y+dy); // 纹理拾取
}
}
out[y * width + x] = sum / 9.0f;
}
}
// 主机代码
cudaArray* cuArray;
cudaMallocArray(&cuArray, &channelDesc, width, height);
cudaMemcpyToArray(cuArray, 0, 0, h_data, size, cudaMemcpyHostToDevice);
cudaBindTextureToArray(texRef, cuArray, channelDesc);
for (int i = 0; i < N; i += 4) {
sum += A[i] + A[i+1] + A[i+2] + A[i+3];
}
cudaOccupancyMaxPotentialBlockSize
计算最优块大小。__shfl_sync
)实现线程块内数据共享。运行时错误检查
#define cudaCheckError() { \
cudaError_t e = cudaGetLastError(); \
if (e != cudaSuccess) { \
printf("CUDA Error: %s\n", cudaGetErrorString(e)); \
exit(-1); \
} \
}
// 在关键API调用后检查错误:
cudaMalloc(&d_A, size); cudaCheckError();
CUDA-MEMCHECK工具
cuda-memcheck --leak-check full ./your_program
cudaGraph_t graph;
cudaGraphExec_t graphExec;
// 创建图并捕获操作
cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
kernel<<<...>>>(...);
cudaMemcpyAsync(...);
cudaStreamEndCapture(stream, &graph);
// 执行图
cudaGraphLaunch(graphExec, stream);