一、CUDA简介:什么是CUDA、GPU与CPU的区别、CUDA的应用领域、学习路线图
1.1 什么是CUDA?——说白了就是让GPU干活的语言
CUDA,全称是Compute Unified Device Architecture,翻译过来叫“统一计算设备架构”。嗯,这个名字有点绕口。我更喜欢把它理解成:让显卡帮你算数的工具。
你想想看,平时我们用显卡打游戏、看视频,那叫“图形渲染”。但显卡里那么多计算单元,闲着也是闲着。NVIDIA就想了个办法——让程序员也能调用这些计算单元来做通用计算。这就是CUDA的由来。
我个人习惯把CUDA看作三样东西的集合:
- 一个编程模型——你写代码的方式变了,得按GPU的思维来
- 一套API——就是那些cudaMalloc、cudaMemcpy之类的函数
- 一个运行时环境——驱动、库、编译器,缺一不可
核心要点:CUDA不是一门新语言,而是C/C++的扩展。你只要在普通C代码里加上一些关键字和API调用,就能让代码跑在GPU上。
1.2 GPU与CPU的区别——一个像博士,一个像民工
这个问题我经常被问到。我一般会打个比方:
CPU是几个超级聪明的博士,什么难题都能解,但数量少。 GPU是一大群普通民工,单个看能力一般,但人多力量大。
具体到硬件设计上:
| 对比项 | CPU | GPU |
|---|---|---|
| 核心数量 | 几个到几十个(比如8核、16核) | 成百上千个(比如3072个CUDA核心) |
| 单个核心能力 | 强,支持乱序执行、分支预测 | 弱,只能做简单运算 |
| 缓存大小 | 大(MB级别),为了减少延迟 | 小(KB级别),为了增加带宽 |
| 适合任务 | 逻辑复杂、分支多的任务 | 数据量大、计算简单的任务 |
| 内存带宽 | 几十GB/s | 几百GB/s到TB/s |
我在项目中遇到过这样一个场景:用CPU处理一张4K图片的像素点,每个像素做同样的颜色转换。CPU跑了200毫秒,换成GPU只用了5毫秒。为什么?因为GPU有几千个核心同时干活,而CPU只能靠几个核心一个一个来。
避坑指南:我曾经以为GPU能加速一切,结果把一个链表遍历放上去跑,反而比CPU慢了10倍。记住:GPU适合数据并行,不适合任务并行。如果你的代码里有大量if-else分支,或者数据依赖很强,GPU可能不是好选择。
1.3 CUDA的应用领域——哪里需要算得快,哪里就有它
说实话,CUDA的应用范围比我十年前入行时想象的广得多。我简单列几个我实际接触过的领域:
- 深度学习——这个最火。PyTorch、TensorFlow底层都用CUDA。训练一个BERT模型,用CPU可能要几个月,用GPU几天就搞定。
- 科学计算——分子动力学模拟、气象预报、石油勘探。我有个朋友做地震波模拟,以前跑一次要一周,用CUDA优化后只要半天。
- 图像处理——实时视频滤镜、医学影像重建。CT图像的三维重建,GPU几乎是标配。
- 金融计算——期权定价、风险分析。华尔街那帮人算得越快,赚钱机会越多。
- 视频编解码——视频转码、直播推流。NVIDIA的NVENC/NVDEC就是基于CUDA的。
个人经验:如果你刚入门,我建议从图像处理或矩阵运算入手。这两个领域的数据结构简单,容易理解并行思想。别一上来就搞深度学习框架,那里面封装太厚,学不到底层东西。
1.4 学习路线图——别急,一步一步来
很多初学者问我:“老师,CUDA难不难?”我的回答是:入门不难,精通很难。你只要会C语言基础,就能开始写CUDA代码。但想写出高性能的代码,需要理解硬件架构、内存层次、线程调度这些底层知识。
我个人建议的学习路线是这样的:
- 第一阶段:环境搭建与基础概念
- 装好NVIDIA驱动、CUDA Toolkit、Visual Studio(或Linux下的gcc)
- 理解GPU硬件架构(SM、SP、Global Memory、Shared Memory)
- 写第一个“Hello World”内核函数
- 第二阶段:线程模型与内存管理
- 搞懂grid、block、thread的关系
- 学会cudaMalloc、cudaMemcpy、cudaFree
- 区分Global Memory、Shared Memory、Register、Local Memory
- 第三阶段:性能优化
- 内存合并访问(Coalesced Access)
- 共享内存的使用与bank conflict
- 线程束发散(Warp Divergence)的避免
- 第四阶段:进阶工具与库
- 学会用NVIDIA Nsight进行性能分析
- 掌握cuBLAS、cuFFT、Thrust等库
- 了解多GPU编程与流(Stream)
我的建议:别贪多。第一阶段花一周,第二阶段花两周,第三阶段花一个月。每个阶段都动手写代码,光看书是学不会CUDA的。我记得自己刚开始时,光是理解grid和block的维度就折腾了好几天。但一旦搞懂了,后面就顺了。
1.5 第一个CUDA程序——感受一下
说了这么多,不如直接看代码。下面是一个最简单的CUDA程序,两个向量相加:
#include <stdio.h>
// 内核函数:在GPU上执行
__global__ void vecAdd(float *A, float *B, float *C, int N) {
int i = threadIdx.x + blockIdx.x * blockDim.x;
if (i < N) {
C[i] = A[i] + B[i];
}
}
int main() {
int N = 1000;
size_t size = N * sizeof(float);
// 分配主机内存
float *h_A = (float*)malloc(size);
float *h_B = (float*)malloc(size);
float *h_C = (float*)malloc(size);
// 初始化数据
for (int i = 0; i < N; i++) {
h_A[i] = i * 1.0f;
h_B[i] = i * 2.0f;
}
// 分配设备内存
float *d_A, *d_B, *d_C;
cudaMalloc(&d_A, size);
cudaMalloc(&d_B, size);
cudaMalloc(&d_C, size);
// 把数据从CPU拷贝到GPU
cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice);
// 启动内核:256个线程一个块,足够多的块
int threadsPerBlock = 256;
int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
vecAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, N);
// 把结果从GPU拷贝回CPU
cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost);
// 验证结果
for (int i = 0; i < 10; i++) {
printf("C[%d] = %f\n", i, h_C[i]);
}
// 释放内存
free(h_A); free(h_B); free(h_C);
cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);
return 0;
}
这段代码看起来和普通C差不多,对吧?关键就多了三样东西:
__global__——告诉编译器这个函数跑在GPU上<<<blocksPerGrid, threadsPerBlock>>>——启动内核时的配置参数cudaMalloc和cudaMemcpy——管理GPU内存
小提示:第一次跑这个程序时,记得检查一下你的GPU是否支持CUDA。在命令行输入nvidia-smi,能看到显卡型号和驱动版本。如果显示“NVIDIA-SMI has failed”,那就是驱动没装好。
1.6 本章小结
嗯,这一章我们聊了CUDA是什么、GPU和CPU的区别、CUDA能干什么、以及怎么学。说白了,CUDA就是让GPU帮你做并行计算的技术。它不是什么神秘的东西,就是C语言加了一些扩展。
下一章我们会深入线程模型,搞明白grid、block、thread到底是怎么工作的。到时候你会看到,原来并行计算可以这么简单粗暴。
记住:动手写代码,比看十遍书都管用。我当年就是一边看文档一边写demo,踩了不少坑,但也学得扎实。你也试试看?