CUDA 并行编程进阶笔记 + C++示例代码(第四章重点)
一、核心笔记(整合前四章核心)
1. SIMD 执行模型深度解析
-
核心本质:单指令多数据,同一指令同时作用于多个数据单元,是 GPU 并行加速的核心原理。
-
关键约束:
-
数据需连续存储:优化内存访问,充分利用 GPU 内存层级;
-
避免分支语句:若核函数含`if-else`,线程束(warp)中部分线程会闲置(执行一个分支时,其他分支线程等待),需尽量简化分支或让分支结果一致;
-
精度取舍优化:用`float`替代`double`、
byte`替代`int,可提升数据吞吐量,间接提升性能。
-
-
适用场景:无复杂分支的规整计算(如向量运算、矩阵乘法、图像像素处理),对应第一章“数据并行”核心场景。
2. 非完全并行示例:素数测试(GPU 实现)
3. GPU 内存管理核心 API(第四章重点)
GPU 内存与 CPU 内存物理分离,需通过专用 API 管理数据传输,核心 API 如下:
| API 函数 | 功能 | 关键参数/注意事项 |
|---|---|---|
|
分配 GPU 设备内存(全局内存) |
第一个参数为`void** |
|
数据拷贝(CPU↔GPU) |
第四个参数指定方向: |
|
释放 GPU 设备内存 |
仅释放`cudaMalloc`分配的设备内存,避免内存泄漏 |
|
获取最近一次 CUDA API 错误 |
用于调试核函数启动、内存操作等错误 |
4. GPU 执行时间测量(cudaEvent)
核函数异步执行(CPU 调用后立即返回),需用`cudaEvent`精准测量 GPU 执行时间:
核心步骤
-
创建事件:
cudaEventCreate(&startEvent)、cudaEventCreate(&stopEvent); -
记录事件:核函数执行前记录`startEvent`,执行后记录`stopEvent`;
-
同步事件:
cudaEventSynchronize(stopEvent)(阻塞 CPU,等待 GPU 执行完成); -
计算耗时:
cudaEventElapsedTime(&duration, startEvent, stopEvent)(单位:毫秒); -
销毁事件:
cudaEventDestroy(startEvent)、cudaEventDestroy(stopEvent),避免资源泄漏。
5. 完全并行示例:向量加法(第四章核心)
并行逻辑
-
每个线程处理 1 对向量元素的加法(数据并行),无分支、无依赖,完美适配 SIMD;
-
线程配置:
-
块内线程数(
threadsPerBlock):通常设为 256/512/1024(需 ≤GPU`maxThreadsPerBlock`); -
网格大小(
blocksPerGrid):(N + threadsPerBlock - 1) / threadsPerBlock,确保覆盖所有元素;
-
-
Guard Clause:
if (i < N),因网格大小可能略大于元素数(如 N=10000,256 线程/块 →40 块 →10240 线程),避免线程索引越界。
数据传输对性能的影响
| 元素数量 | CPU 耗时(ms) | GPU 计算耗时(ms) | GPU 总耗时(含传输,ms) | 加速比(计算) | 加速比(总耗时) |
|---|---|---|---|---|---|
10,000 |
0.021 |
0.089 |
0.145 |
0.24x |
0.15x |
100,000 |
0.280 |
0.123 |
0.445 |
2.27x |
0.63x |
1,000,000 |
2.142 |
0.134 |
2.398 |
15.91x |
0.89x |
10,000,000 |
24.806 |
0.577 |
21.855 |
42.95x |
1.14x |
100,000,000 |
218.270 |
4.955 |
212.297 |
44.04x |
1.02x |
-
结论:数据量越小,内存传输开销占比越高;数据量足够大时,GPU 计算优势才能抵消传输开销。
二、C++(CUDA)示例代码
示例 1:素数测试(GPU vs CPU,非完全并行)
核心功能
验证 GPU 多线程处理含循环/少量分支的任务,对比 CPU 串行执行效率,呼应第四章“非完全并行”示例。
#include <cuda_runtime.h>
#include <iostream>
#include <chrono>
#include <cmath>
using namespace std;
using namespace chrono;
// 错误检查宏
#define CHECK_CUDA_ERR(err) \
if (err != cudaSuccess) { \
cerr << "CUDA错误:" << cudaGetErrorString(err) << "(行号:" << __LINE__ << ")" << endl; \
exit(1); \
}
// CPU串行素数测试
bool checkPrimeCPU(long long num) {
if (num <= 1) return false;
if (num = 2) return true;
if (num % 2 = 0) return false;
for (long long i = 3; i * i <= num; i += 2) {
if (num % i = 0) return false;
}
return true;
}
// GPU核函数:素数测试(每个线程处理一个奇数)
__global__ void checkPrimeGPU(long long start, long long end, bool* d_results) {
// 计算全局线程ID
int tid = threadIdx.x + blockIdx.x * blockDim.x;
// 映射到待测试数字(仅处理奇数,跳过偶数)
long long num = start + (tid * 2);
// 超出范围则退出
if (num > end) return;
bool isPrime = true;
if (num <= 1) {
isPrime = false;
} else if (num = 2) {
isPrime = true;
} else if (num % 2 = 0) {
isPrime = false;
} else {
for (long long i = 3; i * i <= num; i += 2) {
if (num % i = 0) {
isPrime = false;
break;
}
}
}
// 存储结果到设备内存(线程ID对应结果索引)
d_results[tid] = isPrime;
}
int main() {
// 测试范围(100,001 ~ 190,001,仅奇数)
const long long start = 100001;
const long long end = 190001;
const int total_nums = (end - start + 1) / 2; // 奇数个数
// 1. CPU串行测试
auto cpu_start = high_resolution_clock::now();
int cpu_prime_count = 0;
for (long long num = start; num <= end; num += 2) {
if (checkPrimeCPU(num)) cpu_prime_count++;
}
auto cpu_time = duration_cast<microseconds>(high_resolution_clock::now() - cpu_start).count() / 1000.0;
cout << "CPU测试结果:" << cpu_prime_count << " 个素数,耗时:" << cpu_time << " ms" << endl;
// 2. GPU并行测试
// 线程配置
const int threadsPerBlock = 256;
const int blocksPerGrid = (total_nums + threadsPerBlock - 1) / threadsPerBlock;
// 分配设备内存(存储结果)
bool* d_results;
CHECK_CUDA_ERR(cudaMalloc((void**)&d_results, total_nums * sizeof(bool)));
// 分配主机内存(接收结果)
bool* h_results = new bool[total_nums];
// GPU计时
cudaEvent_t gpu_start, gpu_stop;
CHECK_CUDA_ERR(cudaEventCreate(&gpu_start));
CHECK_CUDA_ERR(cudaEventCreate(&gpu_stop));
CHECK_CUDA_ERR(cudaEventRecord(gpu_start, 0));
// 启动核函数(预热执行,不计入时间)
checkPrimeGPU<<<blocksPerGrid, threadsPerBlock>>>(start, end, d_results);
CHECK_CUDA_ERR(cudaDeviceSynchronize());
// 正式执行并计时
CHECK_CUDA_ERR(cudaEventRecord(gpu_start, 0));
checkPrimeGPU<<<blocksPerGrid, threadsPerBlock>>>(start, end, d_results);
CHECK_CUDA_ERR(cudaGetLastError()); // 检查核函数启动错误
CHECK_CUDA_ERR(cudaEventRecord(gpu_stop, 0));
CHECK_CUDA_ERR(cudaEventSynchronize(gpu_stop));
// 计算GPU耗时
float gpu_time;
CHECK_CUDA_ERR(cudaEventElapsedTime(&gpu_time, gpu_start, gpu_stop));
// 设备→主机拷贝结果
CHECK_CUDA_ERR(cudaMemcpy(h_results, d_results, total_nums * sizeof(bool), cudaMemcpyDeviceToHost));
// 统计GPU素数个数
int gpu_prime_count = 0;
for (int i = 0; i < total_nums; ++i) {
if (h_results[i]) gpu_prime_count++;
}
cout << "GPU测试结果:" << gpu_prime_count << " 个素数,耗时:" << gpu_time << " ms" << endl;
cout << "GPU相对CPU加速比:" << cpu_time / gpu_time << "x" << endl;
// 释放资源
delete[] h_results;
CHECK_CUDA_ERR(cudaFree(d_results));
CHECK_CUDA_ERR(cudaEventDestroy(gpu_start));
CHECK_CUDA_ERR(cudaEventDestroy(gpu_stop));
return 0;
}
示例 2:向量加法(完全并行,含内存传输影响)
核心功能
实现 GPU 并行向量加法,对比“仅计算耗时”与“含内存传输总耗时”,呼应第四章“内存传输主导性能”的关键结论。
#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); \
}
// 块大小(适配GPU硬件,≤1024)
const int THREADS_PER_BLOCK = 256;
// CPU串行向量加法
void vectorAddCPU(const float* h_A, const float* h_B, float* h_C, int N) {
for (int i = 0; i < N; ++i) {
h_C[i] = h_A[i] + h_B[i];
}
}
// GPU核函数:向量加法(完全并行)
__global__ void vectorAddGPU(const float* d_A, const float* d_B, float* d_C, int N) {
// 全局线程ID(映射到向量索引)
int i = threadIdx.x + blockIdx.x * blockDim.x;
// Guard Clause:避免索引越界
if (i < N) {
d_C[i] = d_A[i] + d_B[i];
}
}
int main() {
// 向量大小(可修改为10000/100000/10000000/100000000)
const int N = 10'000'000;
const size_t data_size = N * sizeof(float);
// 1. 主机内存分配与初始化
vector<float> h_A(N, 1.23f); // 向量A:全1.23
vector<float> h_B(N, 4.56f); // 向量B:全4.56
vector<float> h_C_CPU(N, 0.0f); // CPU结果
vector<float> h_C_GPU(N, 0.0f); // GPU结果
// 2. CPU串行计算
auto cpu_start = high_resolution_clock::now();
vectorAddCPU(h_A.data(), h_B.data(), h_C_CPU.data(), N);
auto cpu_time = duration_cast<milliseconds>(high_resolution_clock::now() - cpu_start).count();
cout << "CPU串行计算耗时:" << cpu_time << " ms" << endl;
// 3. GPU并行计算(含内存传输)
float *d_A, *d_B, *d_C;
// 分配设备内存
CHECK_CUDA_ERR(cudaMalloc((void**)&d_A, data_size));
CHECK_CUDA_ERR(cudaMalloc((void**)&d_B, data_size));
CHECK_CUDA_ERR(cudaMalloc((void**)&d_C, data_size));
// 计时:总耗时(传输+计算+回传)
auto gpu_total_start = high_resolution_clock::now();
// 主机→设备:拷贝输入向量
CHECK_CUDA_ERR(cudaMemcpy(d_A, h_A.data(), data_size, cudaMemcpyHostToDevice));
CHECK_CUDA_ERR(cudaMemcpy(d_B, h_B.data(), data_size, cudaMemcpyHostToDevice));
// 线程配置
const int blocksPerGrid = (N + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK;
// GPU计算计时(仅计算)
cudaEvent_t compute_start, compute_stop;
CHECK_CUDA_ERR(cudaEventCreate(&compute_start));
CHECK_CUDA_ERR(cudaEventCreate(&compute_stop));
CHECK_CUDA_ERR(cudaEventRecord(compute_start, 0));
// 启动核函数(预热)
vectorAddGPU<<<blocksPerGrid, THREADS_PER_BLOCK>>>(d_A, d_B, d_C, N);
CHECK_CUDA_ERR(cudaDeviceSynchronize());
// 正式计算
vectorAddGPU<<<blocksPerGrid, THREADS_PER_BLOCK>>>(d_A, d_B, d_C, N);
CHECK_CUDA_ERR(cudaGetLastError());
CHECK_CUDA_ERR(cudaEventRecord(compute_stop, 0));
CHECK_CUDA_ERR(cudaEventSynchronize(compute_stop));
// 计算仅计算耗时
float compute_time;
CHECK_CUDA_ERR(cudaEventElapsedTime(&compute_time, compute_start, compute_stop));
// 设备→主机:拷贝结果
CHECK_CUDA_ERR(cudaMemcpy(h_C_GPU.data(), d_C, data_size, cudaMemcpyDeviceToHost));
// 总耗时结束
auto gpu_total_time = duration_cast<milliseconds>(high_resolution_clock::now() - gpu_total_start).count();
// 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仅计算耗时:" << compute_time << " ms" << endl;
cout << "GPU总耗时(含传输):" << gpu_total_time << " ms" << endl;
cout << "结果验证:" << (valid ? "正确" : "错误") << endl;
cout << "GPU计算加速比:" << (double)cpu_time / compute_time << "x" << endl;
cout << "GPU总耗时加速比:" << (double)cpu_time / gpu_total_time << "x" << endl;
// 5. 释放资源
CHECK_CUDA_ERR(cudaFree(d_A));
CHECK_CUDA_ERR(cudaFree(d_B));
CHECK_CUDA_ERR(cudaFree(d_C));
CHECK_CUDA_ERR(cudaEventDestroy(compute_start));
CHECK_CUDA_ERR(cudaEventDestroy(compute_stop));
return 0;
}
示例 3:欧氏距离计算(含 Nsight 性能分析)
核心功能
计算两组 3D 点之间的欧氏距离,演示 GPU 处理复杂数学运算,并用 Nsight Compute 分析性能。
#include <cuda_runtime.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <cmath>
using namespace std;
using namespace chrono;
#define CHECK_CUDA_ERR(err) \
if (err != cudaSuccess) { \
cerr << "CUDA错误:" << cudaGetErrorString(err) << "(行号:" << __LINE__ << ")" << endl; \
exit(1); \
}
// 3D点结构体
struct Point {
float x, y, z;
};
// GPU核函数:计算两组点的欧氏距离
__global__ void calculateEuclideanDistanceGPU(Point* d_lineA, Point* d_lineB, float* d_distances, int numPoints) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < numPoints) {
float dx = d_lineA[idx].x - d_lineB[idx].x;
float dy = d_lineA[idx].y - d_lineB[idx].y;
float dz = d_lineA[idx].z - d_lineB[idx].z;
// 用sqrtf(GPU优化的单精度平方根函数)
d_distances[idx] = sqrtf(dx*dx + dy*dy + dz*dz);
}
}
int main() {
const int numPoints = 1'000'000; // 100万个点
const size_t point_size = numPoints * sizeof(Point);
const size_t dist_size = numPoints * sizeof(float);
// 1. 主机内存初始化
vector<Point> h_lineA(numPoints);
vector<Point> h_lineB(numPoints);
vector<float> h_distances(numPoints, 0.0f);
// 随机生成点坐标(0~100)
srand(time(0));
for (int i = 0; i < numPoints; ++i) {
h_lineA[i] = {rand()%100, rand()%100, rand()%100};
h_lineB[i] = {rand()%100, rand()%100, rand()%100};
}
// 2. 设备内存分配
Point *d_lineA, *d_lineB;
float *d_distances;
CHECK_CUDA_ERR(cudaMalloc((void**)&d_lineA, point_size));
CHECK_CUDA_ERR(cudaMalloc((void**)&d_lineB, point_size));
CHECK_CUDA_ERR(cudaMalloc((void**)&d_distances, dist_size));
// 3. 数据拷贝与计算
CHECK_CUDA_ERR(cudaMemcpy(d_lineA, h_lineA.data(), point_size, cudaMemcpyHostToDevice));
CHECK_CUDA_ERR(cudaMemcpy(d_lineB, h_lineB.data(), point_size, cudaMemcpyHostToDevice));
// 线程配置
const int threadsPerBlock = 256;
const int blocksPerGrid = (numPoints + threadsPerBlock - 1) / threadsPerBlock;
// GPU计时
cudaEvent_t start, stop;
CHECK_CUDA_ERR(cudaEventCreate(&start));
CHECK_CUDA_ERR(cudaEventCreate(&stop));
CHECK_CUDA_ERR(cudaEventRecord(start, 0));
calculateEuclideanDistanceGPU<<<blocksPerGrid, threadsPerBlock>>>(d_lineA, d_lineB, d_distances, numPoints);
CHECK_CUDA_ERR(cudaGetLastError());
CHECK_CUDA_ERR(cudaEventRecord(stop, 0));
CHECK_CUDA_ERR(cudaEventSynchronize(stop));
// 4. 结果回传与耗时计算
float gpu_time;
CHECK_CUDA_ERR(cudaEventElapsedTime(&gpu_time, start, stop));
CHECK_CUDA_ERR(cudaMemcpy(h_distances.data(), d_distances, dist_size, cudaMemcpyDeviceToHost));
cout << "GPU计算100万个3D点欧氏距离耗时:" << gpu_time << " ms" << endl;
cout << "前5个距离结果:";
for (int i = 0; i < 5; ++i) {
cout << h_distances[i] << " ";
}
cout << endl;
// 5. 性能分析(Docker环境)
// 提示:在Docker中启动时添加--cap-add=SYS_ADMIN,执行:ncu -o distance_report ./euclidean_distance
cout << "性能分析命令(Docker):ncu -o distance_report ./euclidean_distance" << endl;
// 6. 释放资源
CHECK_CUDA_ERR(cudaFree(d_lineA));
CHECK_CUDA_ERR(cudaFree(d_lineB));
CHECK_CUDA_ERR(cudaFree(d_distances));
CHECK_CUDA_ERR(cudaEventDestroy(start));
CHECK_CUDA_ERR(cudaEventDestroy(stop));
return 0;
}
编译与性能分析
-
编译命令:
nvcc euclidean_distance.cu -o euclidean_distance -std=c++11; -
Docker 性能分析启动命令:
docker run -it --rm --gpus all --cap-add=SYS_ADMIN -v ./:/code nvidia/cuda:12.0.0-devel-ubuntu20.04 bash; -
容器内性能分析:
ncu -o distance_report ./euclidean_distance; -
导入分析报告:
ncu --import distance_report.ncu-rep; -
预期输出:
GPU计算100万个3D点欧氏距离耗时:0.32 ms 前5个距离结果:22.3607 43.0116 15.2315 37.4166 52.915 性能分析命令(Docker):ncu -o distance_report ./euclidean_distance
三、关键说明
1. 代码与知识点关联
-
示例 1(素数测试):体现“非完全并行”场景,含循环/分支的 GPU 优化,验证线程 ID 映射与结果存储;
-
示例 2(向量加法):核心演示“完全并行”与内存传输影响,Guard Clause 的必要性,以及“一次传输、多次计算”的优化思路;
-
示例 3(欧氏距离):扩展复杂数学运算(平方、平方根),结合 Nsight Compute 性能分析,定位 GPU 利用率瓶颈。