CUDA C++编程指南(7.2)——C++语言扩展之变量内存空间指定符

变量内存空间指定符表示设备上变量的内存位置。

在设备代码中声明的自动变量,如果未使用本节描述的__device____shared____constant__内存空间限定符,通常存放在寄存器中。但在某些情况下,编译器可能会选择将其放置在本地内存中,这可能会对性能产生不利影响,具体细节请参阅设备内存访问。

7.2.1. device

__device__ 内存空间说明符用于声明一个驻留在设备上的变量。

最多可以使用接下来三节中定义的其他内存空间说明符中的一个与__device__一起使用,以进一步指明变量所属的内存空间。如果未指定任何说明符,则该变量:

  • 驻留在全局内存空间中,

  • 其生命周期与创建它的CUDA上下文相同,

  • 每个设备都有一个独立的对象,

  • 可通过网格内所有线程及主机端的运行时库访问(cudaGetSymbolAddress()/ cudaGetSymbolSize()/ cudaMemcpyToSymbol()/ cudaMemcpyFromSymbol())。

7.2.2. constant

__constant__ 内存空间限定符,可选择与 __device__ 一起使用,用于声明一个变量,该变量:

  • 驻留在常量内存空间中,

  • 其生命周期与创建它的CUDA上下文相同,

  • 每个设备都有一个独立的对象,

  • 可通过网格内所有线程及主机端的运行时库访问(cudaGetSymbolAddress()/ cudaGetSymbolSize()/ cudaMemcpyToSymbol()/ cudaMemcpyFromSymbol())。

在存在并发网格访问该常量的情况下,从主机端修改该常量的行为(在该网格生命周期的任何时刻)是未定义的。

7.2.3. shared

__shared__内存空间限定符(可选择与__device__一起使用)用于声明具有以下特性的变量:

  • 驻留在线程块的共享内存空间中

  • 生命周期与代码块相同,

  • 每个块都有一个独立的对象,

  • 仅可从块内的所有线程访问,

  • 没有固定地址。

当在共享内存中声明一个变量作为外部数组时,例如

复制代码
extern __shared__ float shared[];

数组的大小在启动时确定(参见执行配置)。以这种方式声明的所有变量在内存中起始地址相同,因此必须通过偏移量显式管理数组中变量的布局。例如,如果想要实现等同于

复制代码
short array0[128];
float array1[64];
int   array2[256];

在动态分配的共享内存中,可以通过以下方式声明和初始化数组:

复制代码
extern __shared__ float array[];
__device__ void func()      // __device__ or __global__ function
{
    short* array0 = (short*)array;
    float* array1 = (float*)&array0[128];
    int*   array2 =   (int*)&array1[64];
}

请注意,指针需要与其指向的类型对齐,因此例如以下代码将无法工作,因为array1未按4字节对齐。

复制代码
extern __shared__ float array[];
__device__ void func()      // __device__ or __global__ function
{
    short* array0 = (short*)array;
    float* array1 = (float*)&array0[127];
}

7.2.4. grid_constant

对于计算架构大于或等于7.0的情况,__grid_constant__注解用于标注一个非引用类型的const限定__global__函数参数,该参数满足以下条件:

  • 生命周期与网格相同,

  • 对网格是私有的,即主机线程和其他网格(包括子网格)的线程无法访问该对象。

  • 每个网格拥有独立的对象,即网格中的所有线程都访问相同的地址,

  • 是只读的,即修改__grid_constant__对象或其任何子对象属于未定义行为 ,包括mutable成员。

要求:

  • 使用__grid_constant__标注的内核参数必须具有const限定的非引用类型。

  • 所有函数声明必须在任何__grid_constant_参数方面保持一致。

  • 函数模板特化必须与主模板声明在__grid_constant__参数方面保持一致。

  • 函数模板实例化指令必须与主模板声明在__grid_constant__参数方面保持一致。

如果获取了__global__函数参数的地址,编译器通常会在线程本地内存中创建内核参数的副本,并使用该副本的地址,以部分支持C++语义(允许每个线程修改其自身的函数参数本地副本)。通过使用__grid_constant__注解__global__函数参数,可确保编译器不会在线程本地内存中创建内核参数的副本,而是直接使用参数本身的通用地址。避免本地副本可能带来性能提升。

复制代码
__device__ void unknown_function(S const&);
__global__ void kernel(const __grid_constant__ S s) {
   s.x += threadIdx.x;  // Undefined Behavior: tried to modify read-only memory

   // Compiler will _not_ create a per-thread thread local copy of "s":
   unknown_function(s);
}

7.2.5. managed

__managed__内存空间说明符(可选择与__device__一起使用),用于声明一个具有以下特性的变量:

  • 可以从设备和主机代码中引用,例如,可以获取其地址,或者可以直接从设备或主机函数中读取或写入。

  • 生命周期与应用程序相同。

7.2.6. restrict

nvcc 通过 __restrict__ 关键字支持受限指针。

C99中引入了受限指针(restricted pointers),旨在缓解C类语言中存在的别名问题,该问题会阻碍从代码重排序到公共子表达式消除等各种优化。

以下是一个存在别名问题的示例,其中使用受限指针可以帮助编译器减少指令数量:

复制代码
void foo(const float* a,
         const float* b,
         float* c)
{
    c[0] = a[0] * b[0];
    c[1] = a[0] * b[0];
    c[2] = a[0] * b[0] * a[1];
    c[3] = a[0] * a[1];
    c[4] = a[0] * b[0];
    c[5] = b[0];
    ...
}

在C类语言中,指针abc可能存在别名关系,因此通过c的任何写入都可能修改ab的元素。这意味着为了保证功能正确性,编译器不能将a[0]b[0]加载到寄存器中相乘,然后将结果同时存储到c[0]c[1],因为如果a[0]实际上与c[0]是同一内存位置,结果将与抽象执行模型不符。因此编译器无法利用这个公共子表达式。同样地,编译器也不能简单地将c[4]的计算重新排序到靠近c[0]c[1]计算的位置,因为对c[3]的前置写入可能会改变c[4]计算的输入。

通过将abc声明为受限指针,程序员向编译器声明这些指针实际上不存在别名问题,这意味着通过c的写入永远不会覆盖ab的元素。这将函数原型修改如下:

复制代码
void foo(const float* __restrict__ a,
         const float* __restrict__ b,
         float* __restrict__ c);

请注意,所有指针参数都需要设置为受限(restricted),以便编译器优化器能够从中获益。添加__restrict__关键字后,编译器现在可以自由地进行重排序和公共子表达式消除,同时保持与抽象执行模型完全相同的功能:

复制代码
void foo(const float* __restrict__ a,
         const float* __restrict__ b,
         float* __restrict__ c)
{
    float t0 = a[0];
    float t1 = b[0];
    float t2 = t0 * t1;
    float t3 = a[1];
    c[0] = t2;
    c[1] = t2;
    c[4] = t2;
    c[2] = t2 * t3;
    c[3] = t0 * t3;
    c[5] = t1;
    ...
}

此处的影响是减少了内存访问次数和计算量。但由于"缓存"加载和公共子表达式导致寄存器压力增加,这之间需要取得平衡。

由于寄存器压力是许多CUDA代码中的关键问题,使用受限指针可能会对CUDA代码产生负面性能影响,因为会降低占用率。

相关推荐
NAGNIP2 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab3 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab3 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP7 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年7 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼7 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS7 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区8 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈9 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
端平入洛9 小时前
delete又未完全delete
c++