cuda编程笔记(2)--传递参数、设备属性
以下是最简单的带参数的核函数使用过程:
#include<iostream>
#include<cstdio>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
__global__ void add(int a,int b,int *c) {*c = a + b;
}
int main() {int c;int* dev_c;cudaMalloc((void **)&dev_c,sizeof(int));add << <1, 1 >> > (2, 7, dev_c);cudaMemcpy(&c, dev_c, sizeof(int), cudaMemcpyDeviceToHost);//这里会隐式同步,等待核函数执行完printf("2+7=%d\n", c);cudaFree(dev_c);cudaError_t err = cudaDeviceSynchronize();if (err != cudaSuccess) {std::cerr << "CUDA Error: " << cudaGetErrorString(err) << std::endl;}return 0;
}
add的参数:
-
int a
,int b
:两个通过值传递的整型参数。 -
int *c
:一个指针,指向 GPU 设备内存,用于返回结果。
cudaMalloc
的作用
cudaError_t cudaMalloc(void **devPtr, size_t size);
-
作用:在 GPU 设备内存上分配一块大小为
size
字节的内存空间。 -
参数说明:
-
void **devPtr
:一个指向设备指针的指针,cudaMalloc
会将申请到的设备内存地址写入这个指针。 -
size
:需要分配的内存字节数。
-
-
返回值:返回
cudaSuccess
(表示成功),或者其他错误码。
为什么要用 cudaMalloc
?
因为 GPU 上运行的核函数(__global__
)不能访问 CPU 的内存(host memory),所以:
-
要传递结果回 CPU,必须在 GPU 内存中有一块空间存放结果;
-
你不能直接传一个 CPU 指针(如
int* c
)给核函数,否则会产生非法内存访问; -
所以你要用
cudaMalloc
分配一块 GPU 内存(例如int* dev_c
),传给核函数用于写入结果。
示意图:
+----------------+ cudaMemcpy +----------------+
| Host (CPU) | <----------------------> | Device (GPU) |
| int c; | | int* dev_c |
+----------------+ cudaMalloc +----------------+↑dev_c = GPU上的内存地址
在主机代码中,不能对 dev_c
解引用,只能通过 cudaMemcpy
把它里面的数据拷回来后使用。
区域 | 地址空间 | 是否可以解引用该地址? |
---|---|---|
主机内存(Host) | 主机地址 | 只能在 CPU 代码中解引用 |
设备内存(Device) | 显存地址 | 只能在 GPU 核函数中解引用 |
但是在主机上可以对它进行参数传递等不涉及访问内存的操作。
cudaMemcpy
cudaError_t cudaMemcpy(void* dst, const void* src, size_t count, cudaMemcpyKind kind);
参数名 | 说明 |
---|---|
dst | 目标地址(可以是主机或设备内存) |
src | 源地址(可以是主机或设备内存) |
count | 拷贝的字节数 |
kind | 拷贝类型,说明从哪拷到哪(见下表) |
类型 | 含义 |
---|---|
cudaMemcpyHostToDevice | 从主机(Host)拷贝到设备(Device) |
cudaMemcpyDeviceToHost | 从设备(Device)拷贝到主机(Host) |
cudaMemcpyDeviceToDevice | 设备内存之间拷贝(GPU → GPU) |
cudaMemcpyHostToHost | 主机内存之间拷贝(等价于 memcpy ) |
cudaMemcpy
是 同步操作,它会阻塞主机线程直到拷贝完成。这保证了数据安全,但也意味着它会造成 CPU 等待。
⚠️ 如果你希望异步传输,需要使用
cudaMemcpyAsync
并配合 CUDA 流(streams)。
释放显存的函数 —— cudaFree
。
cudaError_t cudaFree(void* devPtr);
在 GPU 上释放之前通过 cudaMalloc
分配的内存。
-
devPtr
:需要释放的设备指针(即之前通过cudaMalloc
得到的 GPU 地址)。 -
返回值:返回一个
cudaError_t
类型的错误码(cudaSuccess
表示成功)。
就像在 CPU 上使用 malloc
后要调用 free
,在 GPU 上使用 cudaMalloc
分配内存后也必须调用 cudaFree
来释放显存:
-
否则会造成 显存泄漏;
-
长时间运行或循环分配会 耗尽 GPU 显存;
-
导致 CUDA 程序崩溃或性能严重下降。
核函数的参数规则
一、核函数参数的基本要求
参数必须是可以被复制(copiable)的数据类型
-
标量类型(如
int
,float
,double
,char
) -
指针类型(如
int*
,float*
) -
结构体或类(必须是 trivially copyable)
不支持的:
-
引用类型(如
int&
) ❌ -
虚函数、继承的类等复杂对象 ❌
二、参数传递方式:值传递(by value)
CUDA 核函数的参数都是 按值传递,即参数会从主机拷贝一份副本传递给设备。
-
标量变量:直接复制一份到 GPU。
-
指针变量:复制的是主机这边的指针值(指向 GPU 内存的地址)。
三、指针指向的内存必须是设备内存
你传给内核的指针必须指向显存(设备内存),否则会导致错误或非法访问:
反例:
int c;
add<<<1, 1>>>(2, 3, &c); // ❌ 错误!主机地址传入设备代码,非法访问
四、核函数参数个数限制
CUDA 对核函数的参数总字节数有限制(包括传入的所有变量)。
-
通常限制为 最多 256 字节(不同架构可能有差异);
-
如果你传入很多参数(如结构体),建议:
-
封装为一个结构体;
-
或使用显存中数据结构代替参数(通过指针传入)。
-
-
不能使用可变参数
五、结构体作为参数的限制
你可以将结构体作为核函数参数传入,但有几点要注意:
-
结构体必须是简单结构体(POD 类型,不能有构造函数、虚函数、继承);
-
会被按值拷贝到设备上;
-
如果结构体内部有指针,那些指针必须指向设备内存。
六、核函数参数必须可在 host 代码中准备好
由于核函数只能从 host 调用,所有参数必须能在 host 端构造并传入(不能传 GPU 上运行时才能生成的数据结构,比如 GPU 上的临时指针等)。
查询设备
cudaGetDeviceCount
cudaError_t cudaGetDeviceCount(int* count);
获取当前系统中 可用的 CUDA 设备数量(即 GPU 的个数)。
参数:
-
count
: 一个指针,用来存放返回的设备数量。
返回值:
-
cudaSuccess
表示成功; -
否则返回错误码(如没有安装驱动、无 GPU 等)。
cudaGetDeviceProperties
cudaError_t cudaGetDeviceProperties(cudaDeviceProp* prop, int device);
功能:
获取指定编号 GPU 的详细属性(例如显存大小、线程数量、SM 架构等)。
参数:
-
prop
: 指向cudaDeviceProp
结构体的指针,用于接收设备信息; -
device
: 设备编号,范围是[0, count - 1]
。
struct cudaDeviceProp
struct cudaDeviceProp {char name[256]; // GPU 名称字符串size_t totalGlobalMem; // 全局显存总大小(单位:字节)size_t sharedMemPerBlock; // 每个线程块可用的共享内存大小int regsPerBlock; // 每个线程块可用的寄存器数量int warpSize; // 一个 warp 中的线程数量(通常为32)size_t memPitch; // 最大内存复制宽度(以字节为单位)int maxThreadsPerBlock; // 每个线程块支持的最大线程数量int maxThreadsDim[3]; // 每个线程块在 x, y, z 三维的最大线程数int maxGridSize[3]; // 每个网格在 x, y, z 三维的最大块数int clockRate; // 时钟频率(kHz)size_t totalConstMem; // 常量内存总大小(字节)int major; // 计算能力主版本号int minor; // 计算能力次版本号size_t textureAlignment; // 纹理对齐要求(字节)size_t texturePitchAlignment; // 对二维纹理中行对齐的要求(字节)int deviceOverlap; // 是否支持设备与主机的重叠执行(1 是,0 否)int multiProcessorCount; // SM(流式多处理器)数量int kernelExecTimeoutEnabled; // 是否启用了内核执行超时(1 是,0 否)int integrated; // 是否为集成 GPU(1 是,0 否)int canMapHostMemory; // 是否支持映射主机内存到设备地址空间int computeMode; // 计算模式(0: 默认,1: 仅主机访问,2: 禁止访问)int maxTexture1D; // 最大 1D 纹理尺寸int maxTexture1DMipmap; // 最大 1D Mipmap 纹理尺寸int maxTexture1DLinear; // 最大 1D 线性纹理尺寸(仅支持一维纹理)int maxTexture2D[2]; // 最大 2D 纹理尺寸(width, height)int maxTexture2DMipmap[2]; // 最大 2D Mipmap 尺寸int maxTexture2DLinear[3]; // 最大 2D 线性纹理尺寸(width, height, pitch)int maxTexture2DGather[2]; // 最大 2D Gather 纹理尺寸int maxTexture3D[3]; // 最大 3D 纹理尺寸(width, height, depth)int maxTexture3DAlt[3]; // 替代的最大 3D 纹理尺寸int maxTextureCubemap; // 最大立方体纹理尺寸int maxTexture1DLayered[2]; // 最大 1D 分层纹理尺寸(width, layers)int maxTexture2DLayered[3]; // 最大 2D 分层纹理尺寸(width, height, layers)int maxTextureCubemapLayered[2]; // 最大立方体分层纹理尺寸(width, layers)int maxSurface1D; // 最大 1D surface 尺寸int maxSurface2D[2]; // 最大 2D surface 尺寸int maxSurface3D[3]; // 最大 3D surface 尺寸int maxSurface1DLayered[2]; // 最大 1D 分层 surface 尺寸int maxSurface2DLayered[3]; // 最大 2D 分层 surface 尺寸int maxSurfaceCubemap; // 最大立方体 surface 尺寸int maxSurfaceCubemapLayered[2]; // 最大立方体分层 surface 尺寸size_t surfaceAlignment; // surface 对齐要求(字节)int concurrentKernels; // 是否支持多个 kernel 并发执行int ECCEnabled; // 是否启用 ECC(错误检查与纠正)int pciBusID; // PCI 总线 IDint pciDeviceID; // PCI 设备 IDint pciDomainID; // PCI 域 IDint tccDriver; // 是否为 TCC 驱动(用于专业显卡如 Tesla)int asyncEngineCount; // 同时支持异步传输与执行的引擎数量int unifiedAddressing; // 是否支持统一虚拟地址空间(UVA)int memoryClockRate; // 显存时钟频率(kHz)int memoryBusWidth; // 显存总线宽度(位)int l2CacheSize; // L2 缓存大小(字节)int maxThreadsPerMultiProcessor; // 每个 SM 支持的最大线程数int streamPrioritiesSupported; // 是否支持流优先级int globalL1CacheSupported; // 是否支持全局 L1 缓存int localL1CacheSupported; // 是否支持本地 L1 缓存size_t sharedMemPerMultiprocessor; // 每个 SM 可用的共享内存大小(字节)int regsPerMultiprocessor; // 每个 SM 可用的寄存器数量int managedMemory; // 是否支持托管内存int isMultiGpuBoard; // 是否为多 GPU 板卡的一部分int multiGpuBoardGroupID; // 多 GPU 板卡上的组 ID
};
配合使用,获取设备属性
#include <iostream>
#include <cuda_runtime.h>int main() {int deviceCount = 0;cudaError_t err = cudaGetDeviceCount(&deviceCount);if (err != cudaSuccess) {std::cerr << "cudaGetDeviceCount failed: " << cudaGetErrorString(err) << std::endl;return -1;}std::cout << "Found " << deviceCount << " CUDA device(s).\n";for (int i = 0; i < deviceCount; ++i) {cudaDeviceProp prop;cudaGetDeviceProperties(&prop, i);std::cout << "\nDevice " << i << ": " << prop.name << "\n";std::cout << " Total Global Memory: " << (prop.totalGlobalMem >> 20) << " MB\n";std::cout << " Compute Capability: " << prop.major << "." << prop.minor << "\n";std::cout << " Multiprocessors: " << prop.multiProcessorCount << "\n";std::cout << " Max Threads Per Block: " << prop.maxThreadsPerBlock << "\n";std::cout << " Max Threads Dim: ("<< prop.maxThreadsDim[0] << ", "<< prop.maxThreadsDim[1] << ", "<< prop.maxThreadsDim[2] << ")\n";std::cout << " Max Grid Size: ("<< prop.maxGridSize[0] << ", "<< prop.maxGridSize[1] << ", "<< prop.maxGridSize[2] << ")\n";}return 0;
}
cudaGetDevice
cudaGetDevice
是 CUDA 运行时 API 中的一个函数,用来获取当前线程所使用的 CUDA 设备编号(Device ID)。它的常见用途是:
-
查询当前使用的是哪一个 GPU。
-
和
cudaSetDevice(int device)
搭配使用,切换或记录设备上下文。
cudaError_t cudaGetDevice(int* device);
参数说明:
-
int* device
:一个指向整数的指针,函数会把当前设备的 ID(从 0 开始)写入这里。
配合使用:cudaSetDevice
cudaSetDevice(1); // 绑定设备1
cudaGetDevice(&id); // 确认当前设备 id 是 1
cudaChooseDevice
cudaChooseDevice
是 CUDA Runtime API 中的一个函数,它的作用是:根据你指定的一些性能偏好,选择最适合的 CUDA 设备(GPU)并返回设备编号(ID)。
cudaError_t cudaChooseDevice(int* device, const cudaDeviceProp* prop);
参数说明:
-
int* device
:返回选择的设备编号。 -
const cudaDeviceProp* prop
:你的“理想设备”配置(可以只设置关键字段)。
使用方式:
cudaDeviceProp desiredProp = {};desiredProp.major = 7; // 至少计算能力为 7.x(如 Volta, Turing, Ampere)desiredProp.totalGlobalMem = 4L * 1024 * 1024 * 1024; // 至少 4GB 显存int chosenDevice = -1;cudaError_t err = cudaChooseDevice(&chosenDevice, &desiredProp);