并行编程与 GPU 开发环境笔记 + C++(CUDA)示例代码

      +

      一、核心笔记(整合第一章+第二章)

      1. 并行编程基础回顾(第一章核心)

      • 定义:将大型问题分解为多个协同执行的子任务,利用多处理器核心同时处理,核心是“分解+同步”。

      • 三大并行类型

        • 数据并行:同一操作作用于不同数据(如像素处理、向量运算);

        • 任务并行:独立子任务并行执行(如晚餐前菜/主菜分工);

        • 流水线并行:依赖型任务分步骤并行(如视频帧处理、水果加工)。

      • GPU 架构核心

        • 流式多处理器(SM):1 个控制单元 + 多个 CUDA 核心,支持 SIMD(单指令多数据);

        • 内存层次(从慢到快):全局内存 → L2 缓存 → L1/共享内存 → 寄存器;

        • 核心优势:支持数千线程并行,适合规整、无复杂分支的计算。

      2. GPU 开发环境核心要求(第二章重点)

      • 硬件前提:必须配备 NVIDIA GPU(仅 NVIDIA 支持 CUDA 编程);

      • 系统要求:Ubuntu 20.04/22.04(NVIDIA 官方支持,兼容性最佳);

      • 核心依赖:NVIDIA 专有驱动(开源 nouveau 驱动不支持 CUDA,必须禁用)。

      3. 关键前置:NVIDIA 驱动安装

      步骤:

      1. 查询适配驱动:访问https://www.nvidia.com/en-us/drivers/[NVIDIA 驱动官网],按 GPU 型号+系统选择(如 RTX 2060 + Ubuntu 20.04);

      2. 系统更新

        sudo apt update && sudo apt upgrade -y
      3. 添加显卡驱动 PPA

        sudo add-apt-repository ppa:graphics-drivers/ppa && sudo apt update
      4. 安装驱动

        • 手动安装(指定版本):sudo apt install nvidia-driver-570(替换为官网查询的版本);

        • 自动安装(推荐新手):sudo ubuntu-drivers autoinstall

      5. 禁用 nouveau 驱动

        sudo nano /etc/modprobe.d/blacklist-nouveau.conf

        添加以下内容:

        blacklist nouveau
        options nouveau modeset=0

        保存退出(Ctrl+S→Ctrl+X),更新 initramfs:

        sudo update-initramfs -u
      6. 验证驱动:重启系统后执行 nvidia-smi,显示 GPU 信息、驱动版本、CUDA 版本则成功。

      4. 两种 CUDA 环境搭建方案(第二章核心)

      方案 核心原理 优点 缺点

      Docker 容器化

      隔离 CUDA 依赖,复用预配置镜像

      1. 无系统冲突,支持多 CUDA 版本;2. 团队环境一致;3. 搭建简单

      1. GPU 调试复杂;2. 需挂载目录共享代码;3. 部分性能分析工具受限

      本地直接安装

      直接在系统中部署 CUDA Toolkit

      1. 调试/性能分析方便;2. 无容器抽象开销;3. 支持完整工具链

      1. 多版本管理复杂;2. 可能与系统库冲突;3. 卸载麻烦

      方案 1:Docker 环境搭建步骤

      1. 安装 Docker 依赖

        sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
      2. 添加 Docker 官方源

        curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
        echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
      3. 安装 Docker

        sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io
      4. 配置免 sudo 运行 Docker

        sudo usermod -aG docker $USER

        注销并重新登录生效,验证:docker run hello-world

      5. 安装 NVIDIA Container Toolkit(GPU 支持)

        curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
        curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
        sudo apt update && sudo apt install -y nvidia-container-toolkit
      6. 配置 Docker GPU 运行时

        sudo nvidia-ctk runtime configure --runtime=docker
        sudo systemctl restart docker
      7. 验证 Docker GPU 访问

        docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi

        显示 GPU 信息则成功。

      方案 2:本地 CUDA Toolkit 安装步骤

      1. 下载 CUDA Toolkit:访问https://developer.nvidia.com/cuda-toolkit[NVIDIA CUDA 官网],选择:

        • 操作系统:Linux → 架构:x86_64 → 发行版:Ubuntu → 版本:20.04 → 安装方式:deb(local);

      2. 执行官网提供的安装命令(示例为 CUDA 12.5):

        wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
        sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
        wget https://developer.download.nvidia.com/compute/cuda/12.5.1/local_installers/cuda-repo-ubuntu2004-12-5-local_12.5.1-555.42.06-1_amd64.deb
        sudo dpkg -i cuda-repo-ubuntu2004-12-5-local_12.5.1-555.42.06-1_amd64.deb
        sudo cp /var/cuda-repo-ubuntu2004-12-5-local/cuda-*-keyring.gpg /usr/share/keyrings/
        sudo apt update && sudo apt install -y cuda-toolkit-12-5
      3. 验证安装:重启系统后执行 nvcc -V,显示 CUDA 编译器版本则成功。

      5. 环境验证核心命令

      验证目标 命令 成功标志

      驱动是否生效

      nvidia-smi

      显示 GPU 型号、驱动版本、CUDA 版本

      CUDA 编译器是否可用

      nvcc -V

      显示 NVIDIA ® Cuda Compiler Driver

      Docker GPU 支持

      docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi

      容器内显示 GPU 信息

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

      示例 1:Docker 环境验证 - CUDA Hello World(SIMD 思想)

      核心功能

      验证 Docker 容器能否正常调用 GPU,体现 CUDA 的线程组织(网格 → 块 → 线程),呼应第一章的 SIMD(单指令多数据)思想。

      代码文件:cuda_hello_docker.cu

      #include <iostream>
      #include <cuda_runtime.h>
      
      // CUDA核函数(设备端执行,GPU上运行)
      // __global__:标识核函数,可被CPU调用,在GPU执行
      __global__ void hello_cuda() {
          // 线程在块内的ID(x维度)
          int thread_id = threadIdx.x;
          // 块在网格内的ID(x维度)
          int block_id = blockIdx.x;
          // 块的大小(x维度)
          int block_size = blockDim.x;
          // 全局线程ID(唯一标识GPU上的每个线程)
          int global_thread_id = block_id * block_size + thread_id;
      
          // 每个线程输出信息(SIMD:所有线程执行同一指令,处理不同ID数据)
          printf("Hello from GPU! Block ID: %d, Thread ID in Block: %d, Global Thread ID: %d\n",
                 block_id, thread_id, global_thread_id);
      }
      
      int main() {
          std::cout << "Hello from CPU! Starting CUDA kernel..." << std::endl;
      
          // 定义CUDA线程组织:1个网格(grid)包含2个块(block),每个块包含4个线程(thread)
          dim3 block_dim(4);  // 块内线程数:x维度4个
          dim3 grid_dim(2);   // 网格内块数:x维度2个(总线程数=2*4=8)
      
          // 启动CUDA核函数(<<<网格维度, 块维度>>>)
          hello_cuda<<<grid_dim, block_dim>>>();
      
          // 等待GPU核函数执行完成(同步CPU和GPU)
          cudaDeviceSynchronize();
      
          // 检查CUDA错误
          cudaError_t err = cudaGetLastError();
          if (err != cudaSuccess) {
              std::cerr << "CUDA Error: " << cudaGetErrorString(err) << std::endl;
              return 1;
          }
      
          std::cout << "CUDA kernel executed successfully!" << std::endl;
          return 0;
      }

      Docker 环境编译与运行步骤

      1. 本地创建代码目录

        mkdir -p ~/cuda_docker_demo && cd ~/cuda_docker_demo
        # 将上述代码保存为 cuda_hello_docker.cu
      2. 启动 Docker 容器(挂载本地目录+启用 GPU)

        docker run -it --rm --runtime=nvidia --gpus all -v ~/cuda_docker_demo:/workspace ubuntu:20.04
        • -v 本地目录:/workspace:将本地代码目录挂载到容器的`/workspace`;

        • --runtime=nvidia --gpus all:启用 GPU 支持。

      3. 容器内安装 CUDA 编译依赖(首次运行需安装):

        apt update && apt install -y cuda-toolkit-12-0  # 按需选择CUDA版本
      4. 编译代码

        cd /workspace
        nvcc cuda_hello_docker.cu -o hello_cuda
      5. 运行程序

        ./hello_cuda
      6. 预期输出

        Hello from CPU! Starting CUDA kernel...
        Hello from GPU! Block ID: 0, Thread ID in Block: 0, Global Thread ID: 0
        Hello from GPU! Block ID: 0, Thread ID in Block: 1, Global Thread ID: 1
        Hello from GPU! Block ID: 0, Thread ID in Block: 2, Global Thread ID: 2
        Hello from GPU! Block ID: 0, Thread ID in Block: 3, Global Thread ID: 3
        Hello from GPU! Block ID: 1, Thread ID in Block: 0, Global Thread ID: 4
        Hello from GPU! Block ID: 1, Thread ID in Block: 1, Global Thread ID: 5
        Hello from GPU! Block ID: 1, Thread ID in Block: 2, Global Thread ID: 6
        Hello from GPU! Block ID: 1, Thread ID in Block: 3, Global Thread ID: 7
        CUDA kernel executed successfully!

      示例 2:本地环境验证 - 向量加法(数据并行)

      核心功能

      实现两个大型向量的逐元素加法,对比 CPU 串行和 GPU 并行的耗时,呼应第一章的数据并行(同一加法操作作用于不同向量元素),验证本地 CUDA 环境。

      代码文件:vector_add.cu

      #include <iostream>
      #include <vector>
      #include <chrono>
      #include <cuda_runtime.h>
      
      using namespace std;
      using namespace chrono;
      
      // 向量大小(大数据集,体现并行优势)
      const int VECTOR_SIZE = 10'000'000;
      
      // CPU串行向量加法
      void cpu_vector_add(const vector<float>& a, const vector<float>& b, vector<float>& c) {
          for (int i = 0; i < VECTOR_SIZE; ++i) {
              c[i] = a[i] + b[i];
          }
      }
      
      // GPU并行向量加法(CUDA核函数,数据并行)
      __global__ void gpu_vector_add(const float* d_a, const float* d_b, float* d_c, int size) {
          // 计算当前线程的全局ID(对应向量索引)
          int idx = blockIdx.x * blockDim.x + threadIdx.x;
          // 避免线程索引超出向量范围
          if (idx < size) {
              d_c[idx] = d_a[idx] + d_b[idx];  // 同一加法操作,处理不同元素(数据并行)
          }
      }
      
      int main() {
          // 1. 初始化CPU端向量(主机内存)
          vector<float> h_a(VECTOR_SIZE, 1.0f);  // 向量a全1
          vector<float> h_b(VECTOR_SIZE, 2.0f);  // 向量b全2
          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;  // GPU设备内存指针
          // 分配GPU全局内存
          cudaMalloc((void**)&d_a, VECTOR_SIZE * sizeof(float));
          cudaMalloc((void**)&d_b, VECTOR_SIZE * sizeof(float));
          cudaMalloc((void**)&d_c, VECTOR_SIZE * sizeof(float));
      
          // 从CPU内存拷贝数据到GPU内存(主机→设备)
          cudaMemcpy(d_a, h_a.data(), VECTOR_SIZE * sizeof(float), cudaMemcpyHostToDevice);
          cudaMemcpy(d_b, h_b.data(), VECTOR_SIZE * sizeof(float), cudaMemcpyHostToDevice);
      
          // 定义CUDA线程组织:块大小=1024(GPU最佳实践),网格大小=ceil(VECTOR_SIZE/1024)
          dim3 block_dim(1024);
          dim3 grid_dim((VECTOR_SIZE + block_dim.x - 1) / block_dim.x);
      
          // GPU核函数执行(计时:仅计算核函数耗时,排除数据拷贝)
          start = high_resolution_clock::now();
          gpu_vector_add<<<grid_dim, block_dim>>>(d_a, d_b, d_c, VECTOR_SIZE);
          cudaDeviceSynchronize();  // 等待GPU执行完成
          auto gpu_time = duration_cast<milliseconds>(high_resolution_clock::now() - start).count();
      
          // 从GPU内存拷贝结果到CPU内存(设备→主机)
          cudaMemcpy(h_c_gpu.data(), d_c, VECTOR_SIZE * sizeof(float), cudaMemcpyDeviceToHost);
      
          // 4. 验证结果正确性(对比前10个元素)
          bool valid = true;
          for (int i = 0; i < 10; ++i) {
              if (abs(h_c_cpu[i] - h_c_gpu[i]) > 1e-5) {
                  valid = false;
                  break;
              }
          }
          cout << "GPU并行向量加法耗时:" << gpu_time << "ms" << endl;
          cout << "结果验证:" << (valid ? "正确" : "错误") << endl;
          cout << "GPU加速比:" << (double)cpu_time / gpu_time << "x" << endl;
      
          // 5. 释放GPU内存
          cudaFree(d_a);
          cudaFree(d_b);
          cudaFree(d_c);
      
          return 0;
      }

      本地环境编译与运行步骤

      1. 保存代码:将上述代码保存为 vector_add.cu

      2. 编译代码(需已安装本地 CUDA Toolkit):

        nvcc vector_add.cu -o vector_add
      3. 运行程序

        ./vector_add
      4. 预期输出

        CPU串行向量加法耗时:35ms
        GPU并行向量加法耗时:1ms
        结果验证:正确
        GPU加速比:35.0x

        (加速比因 GPU 型号不同有所差异,通常为 10-100 倍)

      三、关键说明

      1. 环境适配

        • Docker 环境适合多项目、多 CUDA 版本管理,示例 1 通过挂载目录实现本地代码在容器内运行;

        • 本地环境适合深度开发和性能优化,示例 2 直接调用本地 CUDA 工具链,调试更便捷。

      2. 代码关联理论

        • 示例 1 体现 SIMD 思想:所有 GPU 线程执行同一`printf`指令,仅处理不同线程 ID 数据;

        • 示例 2 体现数据并行:每个 GPU 线程处理向量的一个元素,同一加法操作作用于不同数据,呼应第一章的核心概念。

      3. 常见问题排查

        • 若`nvidia-smi`失败:检查 nouveau 是否禁用,驱动是否安装正确;

        • 若 Docker 容器无法访问 GPU:确认 NVIDIA Container Toolkit 已安装,Docker 服务已重启;

        • 若 CUDA 核函数执行失败:使用 cudaGetLastError() 查询错误信息,检查线程索引是否越界、内存是否分配成功。