一、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代码。但想写出高性能的代码,需要理解硬件架构、内存层次、线程调度这些底层知识。

我个人建议的学习路线是这样的:

  1. 第一阶段:环境搭建与基础概念
    • 装好NVIDIA驱动、CUDA Toolkit、Visual Studio(或Linux下的gcc)
    • 理解GPU硬件架构(SM、SP、Global Memory、Shared Memory)
    • 写第一个“Hello World”内核函数
  2. 第二阶段:线程模型与内存管理
    • 搞懂grid、block、thread的关系
    • 学会cudaMalloc、cudaMemcpy、cudaFree
    • 区分Global Memory、Shared Memory、Register、Local Memory
  3. 第三阶段:性能优化
    • 内存合并访问(Coalesced Access)
    • 共享内存的使用与bank conflict
    • 线程束发散(Warp Divergence)的避免
  4. 第四阶段:进阶工具与库
    • 学会用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>>>——启动内核时的配置参数
  • cudaMalloccudaMemcpy——管理GPU内存

小提示:第一次跑这个程序时,记得检查一下你的GPU是否支持CUDA。在命令行输入nvidia-smi,能看到显卡型号和驱动版本。如果显示“NVIDIA-SMI has failed”,那就是驱动没装好。

1.6 本章小结

嗯,这一章我们聊了CUDA是什么、GPU和CPU的区别、CUDA能干什么、以及怎么学。说白了,CUDA就是让GPU帮你做并行计算的技术。它不是什么神秘的东西,就是C语言加了一些扩展。

下一章我们会深入线程模型,搞明白grid、block、thread到底是怎么工作的。到时候你会看到,原来并行计算可以这么简单粗暴。

记住:动手写代码,比看十遍书都管用。我当年就是一边看文档一边写demo,踩了不少坑,但也学得扎实。你也试试看?