CUDA 基础编程核心笔记 + C++示例代码(第三章重点)

      +

      一、核心笔记(整合前三章核心)

      1. CUDA 核心概念:核函数(Kernel)

      • 定义:GPU 上运行的特殊 C/C++函数,是 CUDA 编程的核心,负责并行执行任务。

      • 关键特征

        • 必须用`__global__`关键字修饰,告知编译器该函数在 GPU(设备端)执行,由 CPU(主机端)调用;

        • 异步执行:核函数调用后 CPU 不会等待其完成,需用`cudaDeviceSynchronize()`强制同步,避免结果未计算完成就被读取;

        • 并行粒度:由“网格(Grid)→ 块(Block)→ 线程(Thread)”三级结构控制,启动时需通过`<<<网格大小, 块大小>>>`配置。

      2. 线程层级结构(Thread-Block-Grid)

      层级 描述 内置变量(x 维度,最常用) 作用

      线程(Thread)

      最小执行单元,每个线程执行核函数代码

      threadIdx.x(块内线程 ID,0 开始)

      唯一标识块内的线程

      块(Block)

      多个线程的集合(可共享块内资源)

      blockIdx.x(网格内块 ID,0 开始)

      唯一标识网格内的块;块间独立,无共享内存

      网格(Grid)

      多个块的集合,对应整个核函数任务

      blockDim.x(块内线程数)

      描述块的维度大小,用于计算全局线程 ID

      • 全局线程 ID 计算(x 维度)tid = threadIdx.x + blockIdx.x * blockDim.x,确保每个线程在整个网格中唯一标识(核心用于数据并行时的索引映射);

      • 硬件映射:块(Block)被分配到 GPU 的流式多处理器(SM),线程被分配到 SM 的 CUDA 核心,一个 SM 可同时执行多个块。

      3. 线程配置关键限制(硬件相关)

      • 每块最大线程数:由 GPU 硬件决定(常见为 1024,可通过设备查询获取);

      • 网格/块维度:支持 1D、2D、3D 配置(用`dim3`类型,如`dim3 grid(2,2)`表示 2×2 的 2D 网格),适配矩阵、图像等多维数据;

      • 资源约束:每个块的线程数会影响共享内存、寄存器的分配,需结合硬件参数优化(避免资源耗尽或利用率不足)。

      4. 设备查询(Programmatic Device Inspection)

      • 作用:通过 CUDA API 获取 GPU 硬件属性,动态适配不同设备,避免因硬件差异导致程序崩溃或性能低下;

      • 核心 API

        • cudaDeviceProp:存储 GPU 属性的结构体(名称、CUDA 算力、共享内存大小等);

        • cudaGetDeviceProperties(&deviceProp, devId):获取指定设备(devId)的属性;

      • 关键查询属性

        • 设备名称(deviceProp.name):确认 GPU 型号;

        • CUDA 算力(deviceProp.major + "." + deviceProp.minor):决定支持的 CUDA 特性;

        • 每块最大线程数(deviceProp.maxThreadsPerBlock):限制块内线程配置;

        • 每块共享内存(deviceProp.sharedMemPerBlock):块内线程共享的高速内存大小。

      5. VS Code + Docker 高效开发环境配置

      核心扩展(必装)

      扩展名称 功能

      C/C++ Extension Pack(Microsoft)

      提供 C/C++语法高亮、智能提示、调试支持

      Nsight Visual Studio Code Edition

      NVIDIA 官方 CUDA 扩展,支持 CUDA 语法解析、调试

      Dev Containers(Microsoft)

      连接 Docker 容器,在容器内开发

      Docker(Microsoft)

      管理 Docker 镜像、容器,简化容器操作

      连接 Docker 容器开发步骤

      1. 启动带 GPU 支持的 CUDA 容器(挂载本地代码目录):

        docker run -it --rm --gpus all -v 本地代码目录:/code nvidia/cuda:12.0.0-devel-ubuntu20.04 bash
      2. 打开 VS Code,通过「Remote Explorer」→「Dev Containers」找到运行的容器,右键「Attach in New Window」;

      3. 在容器中打开 /code 目录(本地代码目录挂载点),安装扩展到容器(点击扩展旁「Install in Dev Container」);

      4. 直接在 VS Code 中编译、运行、调试 CUDA 代码(终端命令与容器内一致)。

      6. 关键注意事项

      • 核函数无返回值(返回类型必须为 void );

      • 线程执行顺序不保证:块与块、线程与线程的调度由 GPU 决定,结果不能依赖执行顺序;

      • 编译命令:用`nvcc`编译器(CUDA Toolkit 自带),后缀为`.cu`(区分普通 C++文件)。

      二、C++(CUDA)示例代码

      示例 1:基础核函数 - Hello CUDA(线程层级验证)

      核心功能

      验证核函数启动、线程层级结构,通过全局线程 ID 唯一标识每个线程,呼应第三章“Hello World”示例的核心逻辑。

      #include <cuda_runtime.h>
      #include <iostream>
      #include <stdio.h>
      
      // CUDA核函数:每个线程输出自身的层级ID
      __global__ void hello_cuda() {
          // 块内线程ID
          int tid_in_block = threadIdx.x;
          // 网格内块ID
          int bid = blockIdx.x;
          // 块内线程数
          int block_threads = blockDim.x;
          // 全局线程ID(唯一标识整个网格中的线程)
          int global_tid = tid_in_block + bid * block_threads;
      
          // 输出线程信息(注意:输出顺序不保证,因线程调度无序)
          printf("Grid内块ID:%d, 块内线程ID:%d, 全局线程ID:%d → Hello CUDA!\n",
                 bid, tid_in_block, global_tid);
      }
      
      int main() {
          std::cout << "CPU端:启动CUDA核函数..." << std::endl;
      
          // 核函数配置:2个块(gridDim.x=2),每个块5个线程(blockDim.x=5)→ 总线程数=2*5=10
          dim3 grid_dim(2);    // 网格维度:1D,2个块
          dim3 block_dim(5);   // 块维度:1D,5个线程
          hello_cuda<<<grid_dim, block_dim>>>();  // 启动核函数
      
          // 等待GPU核函数执行完成(同步CPU与GPU)
          cudaDeviceSynchronize();
      
          // 检查核函数执行错误
          cudaError_t err = cudaGetLastError();
          if (err != cudaSuccess) {
              std::cerr << "CUDA错误:" << cudaGetErrorString(err) << std::endl;
              return 1;
          }
      
          std::cout << "CPU端:核函数执行完成!" << std::endl;
          return 0;
      }

      编译与运行

      • 本地环境:nvcc hello_cuda.cu -o hello_cuda && ./hello_cuda

      • Docker 环境:容器内进入`/code`目录,执行上述相同命令;

      • 预期输出(线程顺序可能不同,全局 ID 唯一):

        CPU端:启动CUDA核函数...
        Grid内块ID:0, 块内线程ID:0, 全局线程ID:0 → Hello CUDA!
        Grid内块ID:0, 块内线程ID:1, 全局线程ID:1 → Hello CUDA!
        Grid内块ID:1, 块内线程ID:0, 全局线程ID:5 → Hello CUDA!
        ...(共10条输出)
        CPU端:核函数执行完成!

      示例 2:设备查询工具(Programmatic Device Inspection)

      核心功能

      获取 GPU 硬件关键属性,动态适配不同设备,呼应第三章“Inspecting Devices”主题,为后续优化提供硬件依据。

      #include <cuda_runtime.h>
      #include <iostream>
      #include <string>
      
      // 错误检查宏(简化CUDA API调用错误处理)
      #define CHECK_CUDA_ERR(err) \
          if (err != cudaSuccess) { \
              std::cerr << "CUDA错误:" << cudaGetErrorString(err) << "(行号:" << __LINE__ << ")" << std::endl; \
              exit(1); \
          }
      
      int main() {
          int device_count;
          cudaError_t err = cudaGetDeviceCount(&device_count);  // 获取GPU设备数量
          CHECK_CUDA_ERR(err);
      
          std::cout << "系统中GPU设备数量:" << device_count << std::endl;
          std::cout << "------------------------GPU设备属性------------------------" << std::endl;
      
          for (int dev_id = 0; dev_id < device_count; ++dev_id) {
              cudaDeviceProp device_prop;
              err = cudaGetDeviceProperties(&device_prop, dev_id);  // 获取指定设备属性
              CHECK_CUDA_ERR(err);
      
              std::cout << "设备" << dev_id << ":" << device_prop.name << std::endl;
              std::cout << "  CUDA算力版本:" << device_prop.major << "." << device_prop.minor << std::endl;
              std::cout << "  每块最大线程数:" << device_prop.maxThreadsPerBlock << std::endl;
              std::cout << "  每块共享内存大小:" << device_prop.sharedMemPerBlock << " 字节" << std::endl;
              std::cout << "  最大网格维度(x/y/z):" << device_prop.maxGridSize[0] << "/"
                        << device_prop.maxGridSize[1] << "/" << device_prop.maxGridSize[2] << std::endl;
              std::cout << "  全局内存大小:" << (double)device_prop.totalGlobalMem / (1024 * 1024 * 1024) << " GB" << std::endl;
              std::cout << "---------------------------------------------------------" << std::endl;
          }
      
          return 0;
      }

      编译与运行

      • 编译命令:nvcc device_query.cu -o device_query

      • 运行命令:./device_query

      • 预期输出(以 RTX 2060 为例):

        系统中GPU设备数量:1
        ------------------------GPU设备属性------------------------
        设备0:NVIDIA GeForce RTX 2060
          CUDA算力版本:7.5
          每块最大线程数:1024
          每块共享内存大小:49152 字节
          最大网格维度(x/y/z):2147483647/65535/65535
          全局内存大小:5.859375 GB
        ---------------------------------------------------------

      示例 3:数据并行实战 - 向量加法(多块多线程优化)

      核心功能

      结合线程层级结构与数据并行思想,用多块多线程实现大型向量加法,适配任意大小向量(自动计算网格/块配置),呼应第一章数据并行与第三章核函数配置。

      #include <cuda_runtime.h>
      #include <iostream>
      #include <vector>
      #include <chrono>
      
      using namespace std;
      using namespace chrono;
      
      // 错误检查宏
      #define CHECK_CUDA_ERR(err) \
          if (err != cudaSuccess) { \
              cerr << "CUDA错误:" << cudaGetErrorString(err) << "(行号:" << __LINE__ << ")" << endl; \
              exit(1); \
          }
      
      // 块大小(适配硬件:通常设为32、64、1024,需≤设备maxThreadsPerBlock)
      const int BLOCK_SIZE = 1024;
      
      // CUDA核函数:向量加法(数据并行)
      __global__ void vector_add_kernel(const float* d_a, const float* d_b, float* d_c, int size) {
          // 计算全局线程ID(映射到向量索引)
          int tid = threadIdx.x + blockIdx.x * blockDim.x;
          // 避免线程索引超出向量范围(网格大小可能略大于向量所需)
          if (tid < size) {
              d_c[tid] = d_a[tid] + d_b[tid];  // 每个线程处理一个元素(数据并行)
          }
      }
      
      // CPU串行向量加法(对比基准)
      void cpu_vector_add(const vector<float>& a, const vector<float>& b, vector<float>& c) {
          for (int i = 0; i < a.size(); ++i) {
              c[i] = a[i] + b[i];
          }
      }
      
      int main() {
          // 向量大小(1000万元素,体现并行优势)
          const int VECTOR_SIZE = 10'000'000;
      
          // 1. 主机端内存分配与初始化
          vector<float> h_a(VECTOR_SIZE, 3.14f);   // 向量a:全为3.14
          vector<float> h_b(VECTOR_SIZE, 2.71f);   // 向量b:全为2.71
          vector<float> h_c_cpu(VECTOR_SIZE, 0.0f); // CPU结果
          vector<float> h_c_gpu(VECTOR_SIZE, 0.0f); // GPU结果
      
          // 2. CPU串行计算
          auto start = high_resolution_clock::now();
          cpu_vector_add(h_a, h_b, h_c_cpu);
          auto cpu_time = duration_cast<milliseconds>(high_resolution_clock::now() - start).count();
          cout << "CPU串行计算耗时:" << cpu_time << " ms" << endl;
      
          // 3. GPU并行计算
          float *d_a, *d_b, *d_c;
          // 设备端内存分配
          CHECK_CUDA_ERR(cudaMalloc((void**)&d_a, VECTOR_SIZE * sizeof(float)));
          CHECK_CUDA_ERR(cudaMalloc((void**)&d_b, VECTOR_SIZE * sizeof(float)));
          CHECK_CUDA_ERR(cudaMalloc((void**)&d_c, VECTOR_SIZE * sizeof(float)));
      
          // 主机→设备数据拷贝
          CHECK_CUDA_ERR(cudaMemcpy(d_a, h_a.data(), VECTOR_SIZE * sizeof(float), cudaMemcpyHostToDevice));
          CHECK_CUDA_ERR(cudaMemcpy(d_b, h_b.data(), VECTOR_SIZE * sizeof(float), cudaMemcpyHostToDevice));
      
          // 计算网格大小:ceil(VECTOR_SIZE / BLOCK_SIZE),确保所有元素被覆盖
          dim3 block_dim(BLOCK_SIZE);
          dim3 grid_dim((VECTOR_SIZE + BLOCK_SIZE - 1) / BLOCK_SIZE);
          cout << "GPU配置:网格大小=" << grid_dim.x << ",块大小=" << block_dim.x << endl;
      
          // 核函数执行与计时
          start = high_resolution_clock::now();
          vector_add_kernel<<<grid_dim, block_dim>>>(d_a, d_b, d_c, VECTOR_SIZE);
          CHECK_CUDA_ERR(cudaGetLastError()); // 检查核函数启动错误
          CHECK_CUDA_ERR(cudaDeviceSynchronize()); // 同步GPU
          auto gpu_time = duration_cast<milliseconds>(high_resolution_clock::now() - start).count();
      
          // 设备→主机数据拷贝(结果回传)
          CHECK_CUDA_ERR(cudaMemcpy(h_c_gpu.data(), d_c, VECTOR_SIZE * sizeof(float), cudaMemcpyDeviceToHost));
      
          // 4. 结果验证
          bool result_valid = true;
          for (int i = 0; i < 10; ++i) { // 验证前10个元素(避免遍历耗时)
              if (abs(h_c_cpu[i] - h_c_gpu[i]) > 1e-5) {
                  result_valid = false;
                  break;
              }
          }
      
          // 输出结果
          cout << "GPU并行计算耗时:" << gpu_time << " ms" << endl;
          cout << "结果验证:" << (result_valid ? "正确" : "错误") << endl;
          cout << "GPU加速比:" << (double)cpu_time / gpu_time << "x" << endl;
      
          // 5. 释放设备内存
          cudaFree(d_a);
          cudaFree(d_b);
          cudaFree(d_c);
      
          return 0;
      }

      编译与运行

      • 编译命令:nvcc vector_add_cuda.cu -o vector_add_cuda

      • 运行命令:./vector_add_cuda

      • 预期输出

        CPU串行计算耗时:32 ms
        GPU配置:网格大小=9766,块大小=1024
        GPU并行计算耗时:1 ms
        结果验证:正确
        GPU加速比:32.0x

      三、关键说明

      1. 代码与知识点关联

      • 示例 1 核心:验证核函数`__global__修饰符、线程层级内置变量、核函数启动配置<<<grid, block>>>`;

      • 示例 2 核心:掌握`cudaDeviceProp`结构体与设备查询 API,为后续线程配置、内存优化提供硬件依据;

      • 示例 3 核心:结合数据并行思想(每个线程处理一个向量元素)与动态网格/块配置,适配任意大小数据,体现 CUDA 编程的实用性。

      2. 开发环境优化建议

      • 用 VS Code + Nsight 扩展:支持 CUDA 语法高亮、智能提示、调试(断点调试核函数);

      • Docker 环境优势:无需担心 CUDA 版本与系统冲突,团队共享统一环境,避免“我这能跑”问题;

      • 错误处理:使用`CHECK_CUDA_ERR`宏捕获 CUDA API 错误,大幅降低调试难度(第三章未详细讲,但为实战必备)。

      3. 常见问题排查

      • 核函数无输出/输出乱序:忘记`cudaDeviceSynchronize()`,或线程执行顺序依赖错误(需通过全局 ID 保证逻辑顺序,而非执行顺序);

      • 编译错误“identifier “threadIdx” is undefined”:未用`nvcc`编译,或核函数未加`__global__`修饰;

      • 运行错误“too many resources requested for launch”:块内线程数超过设备`maxThreadsPerBlock`(需通过设备查询调整`BLOCK_SIZE`)。