cpp
`````````#include <stdio.h>
#include <cuda_runtime.h>
// CUDA核函数:向量加法
// 每个线程处理一个元素
__global__ void vectorAdd(const float* a, const float* b, float* c, int n) {
// 计算全局线程索引
int idx = blockIdx.x * blockDim.x + threadIdx.x;
// 确保不越界
if (idx < n) {
c[idx] = a[idx] + b[idx];
}
}
// 主函数
int main() {
int n = 1024 * 1024; // 向量大小:1M个元素
size_t size = n * sizeof(float);
printf("CUDA向量加法程序\n");
printf("向量大小: %d\n", n);
// 1. 分配主机内存
float *h_a = (float*)malloc(size);
float *h_b = (float*)malloc(size);
float *h_c = (float*)malloc(size);
// 2. 初始化主机数据
for (int i = 0; i < n; i++) {
h_a[i] = (float)i;
h_b[i] = (float)i * 2.0f;
}
// 3. 分配设备内存
float *d_a, *d_b, *d_c;
cudaMalloc((void**)&d_a, size);
cudaMalloc((void**)&d_b, size);
cudaMalloc((void**)&d_c, size);
// 4. 将数据从主机复制到设备
cudaMemcpy(d_a, h_a, size, cudaMemcpyHostToDevice);
cudaMemcpy(d_b, h_b, size, cudaMemcpyHostToDevice);
// 5. 启动CUDA核函数
int threadsPerBlock = 256;
int blocksPerGrid = (n + threadsPerBlock - 1) / threadsPerBlock;
printf("\n启动核函数...\n");
printf("每个线程块包含 %d 个线程\n", threadsPerBlock);
printf("总共启动 %d 个线程块\n", blocksPerGrid);
vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_a, d_b, d_c, n);
// 6. 等待GPU完成计算并检查错误
cudaError_t err = cudaGetLastError();
if (err != cudaSuccess) {
printf("CUDA错误: %s\n", cudaGetErrorString(err));
return -1;
}
// 7. 将结果从设备复制回主机
cudaMemcpy(h_c, d_c, size, cudaMemcpyDeviceToHost);
// 8. 验证结果
printf("\n验证结果...\n");
bool success = true;
for (int i = 0; i < 10; i++) { // 只检查前10个元素
float expected = h_a[i] + h_b[i];
if (fabs(h_c[i] - expected) > 1e-5) {
printf("错误: a[%d]=%.2f, b[%d]=%.2f, c[%d]=%.2f (期望值: %.2f)\n",
i, h_a[i], i, h_b[i], i, h_c[i], expected);
success = false;
break;
}
}
if (success) {
printf("✓ 验证通过!前10个元素计算正确。\n");
printf("示例: a[0]=%.2f + b[0]=%.2f = c[0]=%.2f\n", h_a[0], h_b[0], h_c[0]);
}
// 9. 释放内存
free(h_a);
free(h_b);
free(h_c);
cudaFree(d_a);
cudaFree(d_b);
cudaFree(d_c);
printf("\n程序执行完成!\n");
return 0;
}
cpp
> (void**)&d_a 这里为什么是这个样子的?
✦ cudaMalloc 使用 (void**)&d_a 是因为 cudaMalloc 函数的签名要求传入一个 void** 类型的参数。
让我解释一下原因:
1. cudaMalloc 函数签名
cudaError_t cudaMalloc(void** devPtr, size_t size);
- 第一个参数是 void** 类型(指向指针的指针)
- 第二个参数是分配的内存大小
2. 为什么需要指向指针的指针?
cudaMalloc 需要修改指针 d_a 本身的值(让它指向设备内存地址),而不仅仅是修改指针指向的内容。
在 C/C++ 中,如果要修改一个变量的值,需要传递它的地址:
- 修改普通变量 → 传递 &变量(地址)
- 修改指针变量 → 传递 &指针(指针的地址)
3. 类型转换过程
float *d_a; // d_a 的类型是 float*
- &d_a 的类型是 float**(指向 float 指针的指针)
- cudaMalloc 要求 void**
- 所以需要强制转换:(void**)&d_a
4. 类比:标准库的 malloc
对比一下 CPU 端的内存分配:
// CPU 端 - malloc 返回值
float *d_a = (float*)malloc(size); // 直接返回指针
// GPU 端 - cudaMalloc 通过参数返回
float *d_a;
cudaMalloc((void**)&d_a, size); // 通过参数返回指针地址
5. 完整示例
float *d_a; // 声明一个 float 指针
cudaMalloc((void**)&d_a, size); // 将 float** 转换为 void** 传递
// 执行后,d_a 被赋值为设备内存的地址
✦ 简单来说:因为 `cudaMalloc` 需要修改指针 `d_a` 的值,让它指向新分配的设备内存,所以必须传递 `d_a`
的地址(即 `&d_a`),并进行类型转换。
成!编译和执行都已成功。
总结:
-
编译命令:
nvcc vector_add.cu -o vector_add
-
执行命令:
./vector_add
-
程序输出:
- 程序处理了 1,048,576 (1M) 个元素的向量加法
- 使用了 256 个线程/块,共 4096 个线程块
- 计算结果验证通过 ✓
✦ CUDA 程序成功在 GPU 上运行并完成了向量加法计算!