CUDA 基础编程核心笔记 + C++示例代码(第三章重点)
一、核心笔记(整合前三章核心)
1. CUDA 核心概念:核函数(Kernel)
-
定义:GPU 上运行的特殊 C/C++函数,是 CUDA 编程的核心,负责并行执行任务。
-
关键特征:
-
必须用`__global__`关键字修饰,告知编译器该函数在 GPU(设备端)执行,由 CPU(主机端)调用;
-
异步执行:核函数调用后 CPU 不会等待其完成,需用`cudaDeviceSynchronize()`强制同步,避免结果未计算完成就被读取;
-
并行粒度:由“网格(Grid)→ 块(Block)→ 线程(Thread)”三级结构控制,启动时需通过`<<<网格大小, 块大小>>>`配置。
-
2. 线程层级结构(Thread-Block-Grid)
| 层级 | 描述 | 内置变量(x 维度,最常用) | 作用 |
|---|---|---|---|
线程(Thread) |
最小执行单元,每个线程执行核函数代码 |
|
唯一标识块内的线程 |
块(Block) |
多个线程的集合(可共享块内资源) |
|
唯一标识网格内的块;块间独立,无共享内存 |
网格(Grid) |
多个块的集合,对应整个核函数任务 |
|
描述块的维度大小,用于计算全局线程 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 容器开发步骤
-
启动带 GPU 支持的 CUDA 容器(挂载本地代码目录):
docker run -it --rm --gpus all -v 本地代码目录:/code nvidia/cuda:12.0.0-devel-ubuntu20.04 bash -
打开 VS Code,通过「Remote Explorer」→「Dev Containers」找到运行的容器,右键「Attach in New Window」;
-
在容器中打开
/code目录(本地代码目录挂载点),安装扩展到容器(点击扩展旁「Install in Dev Container」); -
直接在 VS Code 中编译、运行、调试 CUDA 代码(终端命令与容器内一致)。
二、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;
}
示例 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;
}
三、关键说明
1. 代码与知识点关联
-
示例 1 核心:验证核函数`__global__
修饰符、线程层级内置变量、核函数启动配置<<<grid, block>>>`; -
示例 2 核心:掌握`cudaDeviceProp`结构体与设备查询 API,为后续线程配置、内存优化提供硬件依据;
-
示例 3 核心:结合数据并行思想(每个线程处理一个向量元素)与动态网格/块配置,适配任意大小数据,体现 CUDA 编程的实用性。