异构计算与CUDA概述
欢迎进入《异构计算CUDA并行编程深度精讲》的第一章。在传统观念中,CPU 的性能提升主要依赖主频提高与核心数增加,但功耗墙与存储墙的出现让这条路越走越窄。与此同时,GPU 凭借数千个轻量级核心在图像渲染中积累的并行吞吐能力,逐渐被用于通用计算。异构计算由此成为高性能计算领域的主流范式——让不同特性的处理器各司其职,协同完成复杂任务。
1.1 异构计算的核心思想
异构计算(Heterogeneous Computing)指在一个系统中集成多种指令集架构或微架构不同的处理器,由主机端(Host)与设备端(Device)协同工作。在典型的 CUDA 系统中,CPU 作为主机负责逻辑控制与串行任务,GPU 作为设备负责大规模数据并行计算。两者通过 PCIe 总线或 NVLink 互联,各自拥有独立的存储空间。
关键认知:异构计算不是简单地把任务丢给 GPU,而是将任务拆分为“适合串行控制”的部分与“适合并行吞吐”的部分。CPU 擅长分支预测、乱序执行与低延迟响应;GPU 擅长大量线程的吞吐计算,但单个线程延迟较高。合理划分任务边界,才能发挥异构系统的整体效率。
从硬件视角看,GPU 内部包含多个流多处理器(SM),每个 SM 又包含若干 CUDA 核心、共享内存、寄存器堆与调度器。这种层级化结构决定了 CUDA 编程模型必须暴露线程层级、内存层级与同步语义,才能让开发者精细控制硬件资源。
1.2 CUDA 编程模型与执行流程
CUDA(Compute Unified Device Architecture)是 NVIDIA 提供的异构并行计算平台与编程模型。其核心抽象包括:线程层级结构、内存层级结构与同步语义。一个典型的 CUDA 程序由主机代码与设备代码组成,设备代码通过 __global__ 修饰的内核函数(Kernel)表达。
内核启动时,开发者需要指定网格(Grid)与线程块(Block)的维度。GPU 硬件将线程块调度到 SM 上执行,每个 SM 可以同时驻留多个线程块。线程块内的线程通过共享内存协作,并通过 __syncthreads() 进行同步。这种两层并行结构既保证了可扩展性(不同 GPU 的 SM 数量不同),又提供了细粒度的数据共享机制。
标准 CUDA 执行流程
- 分配设备内存:调用
cudaMalloc在 GPU 显存中分配输入/输出缓冲区。 - 数据拷贝:使用
cudaMemcpy将输入数据从主机内存传输到设备显存。 - 启动内核:以
kernel<<<grid, block>>>(args)语法启动并行计算。 - 同步与回拷:调用
cudaDeviceSynchronize等待内核完成,再将结果拷回主机。 - 释放资源:调用
cudaFree释放设备内存。
// 典型 CUDA 程序骨架
#include <cuda_runtime.h>
#include <stdio.h>
__global__ void vectorAdd(const float *A, const 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;
size_t bytes = n * sizeof(float);
// 1. 分配设备内存
float *d_A, *d_B, *d_C;
cudaMalloc(&d_A, bytes);
cudaMalloc(&d_B, bytes);
cudaMalloc(&d_C, bytes);
// 2. 主机数据准备与拷贝
float *h_A = (float*)malloc(bytes);
float *h_B = (float*)malloc(bytes);
// ... 初始化 h_A, h_B ...
cudaMemcpy(d_A, h_A, bytes, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, bytes, cudaMemcpyHostToDevice);
// 3. 启动内核
int threads = 256;
int blocks = (n + threads - 1) / threads;
vectorAdd<<<blocks, threads>>>(d_A, d_B, d_C, n);
// 4. 同步并回拷结果
cudaDeviceSynchronize();
cudaMemcpy(h_A, d_C, bytes, cudaMemcpyDeviceToHost);
// 5. 释放资源
cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);
free(h_A); free(h_B);
return 0;
}
1.3 典型应用场景与性能特征
异构计算并非万能药。以下场景最适合 CUDA 加速:
| 应用领域 | 并行特征 | 典型加速比 |
|---|---|---|
| 矩阵乘法 / 线性代数 | 数据高度规整,计算密度大 | 10× ~ 100× |
| 图像处理 / 计算机视觉 | 像素级独立操作,天然并行 | 20× ~ 80× |
| 深度学习训练与推理 | 卷积/矩阵运算为主,批量并行 | 5× ~ 50× |
| 分子动力学模拟 | 粒子间相互作用,邻域列表 | 10× ~ 60× |
| 金融风险分析(蒙特卡洛) | 大量独立随机路径 | 30× ~ 200× |
判断准则:如果任务满足“数据并行、计算密集、算术强度高、分支少”四个特征,CUDA 通常能带来显著收益。反之,如果任务以串行依赖为主、分支发散严重或数据量过小(无法掩盖传输开销),则 CPU 可能更合适。
1.4 常见误区与学习路径
误区一:认为 GPU 主频高所以单线程快。实际上 GPU 单核主频低于 CPU,其优势在于数千线程的并行吞吐。若只写单线程代码,GPU 反而更慢。
误区二:忽略数据传输开销。PCIe 带宽远低于显存带宽,频繁的主机-设备拷贝会严重拖累性能。应尽量减少传输次数,或使用零拷贝/统一虚拟内存技术。
误区三:过度使用共享内存。共享内存容量有限(通常几十 KB/SM),使用不当会导致线程块驻留数下降,反而降低并行度。
本课程共 30 章,后续将依次深入 CUDA 线程模型、内存优化、原子操作、流与并发、多 GPU 编程、性能分析工具(Nsight)、以及若干实战项目。每一章都会配合可运行的代码示例与性能剖析,帮助你建立从“能跑”到“跑得快”的完整能力。