2. 内存结构 & 核函数基本说明
内存结构
对于 CPU + GPU 的异构模型,两者内存是隔离的 (当然了,CPU有自己的内存,GPU有自己的显存),因此我们会有对应的 CUDA 函数对设备进行内存分配与复制:
- cudaMalloc: 分配内存
- cudaMemcpy: 复制内存
- cudaMemset: 设置内存
- cudaFree: 释放内存
cuda 函数统一返回 cudaError_t 方便主机端进行错误处理,为了更好显示,runtime api 也提供了一些必要的转换方法,比如,具体参考官方文档
1 | // Returns the string representation of an error code enum name. |
从软件层面理解 GPU 内存结构,是这样的:

一个核函数对应一个 Grid,而一个 Grid 中可以用多个 Block 和 Thread (这里 Block 对应硬件层面的 SM,而多个 Thread 会捆绑为一个 Warp 对应硬件层面的 SP 进行执行),同时注意上图,在同一个 Block 内多个 Thread 是共享内存的,而不同 Block 的线程不能相互影响,这可以和硬件层面的 SM 架构做直接的对应。
当然,从前面 Hello World 的示例程序我们也知道,不同 Thread 之间通过
- blockIdx: 线程所在的 Block 对应 Grid 内部的索引号(最多可以有 3 维,xyz)
- threadIdx: 线程对应 Block 内部的索引号(最多可以有 3 维,xyz)有索引
核函数基本说明
简单举个代码例子,注意在计算 Block 数量的时候应当取上确界。
1 |
|
输出结果为:
1 | CUDA Block 和 Thread 数量说明 |
从上面代码可以看到,我们采用 __global__ 来声明核函数,这是 CUDA 规定的,具体而言:
| 说明符 | 执行 | 调用 |
|---|---|---|
| global | 设备执行 | 主机/设备 都可调用 |
| device | 设备执行 | 设备调用 |
| host | 主机执行 | 主机调用 |
而且这里有个特殊的情况就是有些函数可以同时定义为 device 和 host ,这种函数可以同时被设备和主机端的代码调用,主机端代码调用函数很正常,设备端调用函数与C语言一致,但是要声明成设备端代码,告诉nvcc编译成设备机器码,同时声明主机端设备端函数,那么就要告诉编译器,生成两份不同设备的机器码。
调用核函数时就是简单的 kernel<<<GridDim, BlockDim>>>() 非常符合直觉,唯一值得注意的是:
错误处理
我们前面提到了 C++ 常见的通过返回值进行错误处理,我们可以简单定义一个宏,帮助快速定位错误地方:
1 |
Profiling
2026了,当然得采用现代的 Nsight 而不是 nvprof 来做性能分析,直接在终端使用:
1 | nsys profile --stats=true ./memory |
--status=true 会在终端直接打印 API 的耗时和核函数执行时间统计汇总,比如像下面文本:
1 | [4/8] Executing 'osrt_sum' stats report |
同时还会生成对应的以 .nsys-rep 结尾的文件,可以使用 nsight-compute 打开:
1 | nsys-ui ./report1.nsys-rep |

说些什么吧!