并行编程与 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 驱动安装
步骤:
-
查询适配驱动:访问https://www.nvidia.com/en-us/drivers/[NVIDIA 驱动官网],按 GPU 型号+系统选择(如 RTX 2060 + Ubuntu 20.04);
-
系统更新:
sudo apt update && sudo apt upgrade -y -
添加显卡驱动 PPA:
sudo add-apt-repository ppa:graphics-drivers/ppa && sudo apt update -
安装驱动:
-
手动安装(指定版本):
sudo apt install nvidia-driver-570(替换为官网查询的版本); -
自动安装(推荐新手):
sudo ubuntu-drivers autoinstall;
-
-
禁用 nouveau 驱动:
sudo nano /etc/modprobe.d/blacklist-nouveau.conf添加以下内容:
blacklist nouveau options nouveau modeset=0
保存退出(Ctrl+S→Ctrl+X),更新 initramfs:
sudo update-initramfs -u -
验证驱动:重启系统后执行
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 环境搭建步骤
-
安装 Docker 依赖:
sudo apt install -y apt-transport-https ca-certificates curl software-properties-common -
添加 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 -
安装 Docker:
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io -
配置免 sudo 运行 Docker:
sudo usermod -aG docker $USER注销并重新登录生效,验证:
docker run hello-world。 -
安装 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 -
配置 Docker GPU 运行时:
sudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker -
验证 Docker GPU 访问:
docker run --rm --runtime=nvidia --gpus all ubuntu nvidia-smi显示 GPU 信息则成功。
方案 2:本地 CUDA Toolkit 安装步骤
-
下载 CUDA Toolkit:访问https://developer.nvidia.com/cuda-toolkit[NVIDIA CUDA 官网],选择:
-
操作系统:Linux → 架构:x86_64 → 发行版:Ubuntu → 版本:20.04 → 安装方式:deb(local);
-
-
执行官网提供的安装命令(示例为 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 -
验证安装:重启系统后执行
nvcc -V,显示 CUDA 编译器版本则成功。
二、C++(CUDA)示例代码
示例 1:Docker 环境验证 - CUDA Hello World(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 环境编译与运行步骤
-
本地创建代码目录:
mkdir -p ~/cuda_docker_demo && cd ~/cuda_docker_demo # 将上述代码保存为 cuda_hello_docker.cu -
启动 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 支持。
-
-
容器内安装 CUDA 编译依赖(首次运行需安装):
apt update && apt install -y cuda-toolkit-12-0 # 按需选择CUDA版本 -
编译代码:
cd /workspace nvcc cuda_hello_docker.cu -o hello_cuda -
运行程序:
./hello_cuda -
预期输出:
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:本地环境验证 - 向量加法(数据并行)
代码文件: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;
}
三、关键说明
-
环境适配:
-
Docker 环境适合多项目、多 CUDA 版本管理,示例 1 通过挂载目录实现本地代码在容器内运行;
-
本地环境适合深度开发和性能优化,示例 2 直接调用本地 CUDA 工具链,调试更便捷。
-
-
代码关联理论:
-
示例 1 体现 SIMD 思想:所有 GPU 线程执行同一`printf`指令,仅处理不同线程 ID 数据;
-
示例 2 体现数据并行:每个 GPU 线程处理向量的一个元素,同一加法操作作用于不同数据,呼应第一章的核心概念。
-
-
常见问题排查:
-
若`nvidia-smi`失败:检查 nouveau 是否禁用,驱动是否安装正确;
-
若 Docker 容器无法访问 GPU:确认 NVIDIA Container Toolkit 已安装,Docker 服务已重启;
-
若 CUDA 核函数执行失败:使用
cudaGetLastError()查询错误信息,检查线程索引是否越界、内存是否分配成功。
-