常量内存和只读缓存
目录摘要常量内存常量缓存特性分配方式使用常量内存实现一维模板只读缓存分配方式实验总结摘要本文将介绍常量内存和只读缓存然后对比其差异常量内存常量内存是一种专用的全局内存区域用于存放只读数据。对于GPU端的内核代码常量内存是完全只读的GPU线程只能读取不能写入。主机端CPU可以在内核启动之前通过cudaMemcpyToSymbol等API向常量内存写入数据或者在内核执行完毕后读取数据。常量内存是位于片外而不是片上片上的是常量缓存关于常量缓存我们是查不到的其实对我们也没啥可编程的作用这个是硬件的作用不像共享内存一样开放给我们进行编程这样知道其大小就有用我们只能知道常量内存的大小cout常量内存大小prop.totalConstMemendl;由此可得常量内存大小是64KB常量缓存特性常量内存是存放数据的地方接下来讲的特性是属于常量缓存的常量缓存具有单周期广播性质也就是当请求的地址是同一个的时候常量缓存能够把数据广播给32个线程第 1 步Warp Scheduler 发射指令Warp Scheduler 从就绪的 Warp 中挑中它发射一条LDCLoad Constant指令给LSU。第 2 步LSU 把请求路由给常量缓存LSU 收到指令后收集这 32 个线程的地址。但它不做合并常量内存走的是常量缓存通路不走 L1而是直接把这 32 个地址发送给常量缓存控制器。第 3 步常量缓存控制器判断是否广播常量缓存控制器检查这 32 个地址如果所有地址都相同→ 触发广播。控制器直接从常量缓存的 SRAM 中取出这份数据通过专用的广播总线在一个时钟周期内发送给所有 32 个线程。LSU 负责把数据写入各线程的目标寄存器。如果地址不同→ 广播失效。控制器只能串行化处理这些请求一个接一个地从常量缓存或更底层的 DRAM读取数据。此时性能暴跌甚至可能比普通全局内存还慢。第 4 步如果常量缓存缺失如果请求的数据不在常量缓存的SRAM 里控制器会把请求转发给L2 缓存L2 再未命中就从片外 DRAM 的常量内存区域那 64KB搬来整个 128 字节 Cache Line。数据进入常量缓存后再按第 3 步的逻辑返回给 LSU。注意一旦这32个线程要的是不同的地址就会变串行如果是全局显存LSU拿到数据之后不是一个周期就能写到32个线程的寄存器的需要多个周期分配方式常量内存必须在所有函数外部以__constant__关键字声明。它的大小是静态固定的且必须在主机端通过专用 API 来初始化或更新数据。① 声明常量内存变量// 全局作用域文件作用域内可见 __constant__ float weights[256];② 从主机端拷贝数据到常量内存必须使用cudaMemcpyToSymbol不能用普通的cudaMemcpy。float h_weights[256]; // ... 初始化 h_weights ... cudaMemcpyToSymbol(weights, h_weights, sizeof(h_weights));为什么不能用cudaMemcpy因为__constant__变量不是普通的指针它没有可以被cudaMemcpy识别的“设备地址”。cudaMemcpyToSymbol内部会自动处理这种特殊的地址映射。③ 从主机端读取常量内存中的数据使用cudaMemcpyFromSymbol同样不能直接用cudaMemcpy。float h_copy[256]; cudaMemcpyFromSymbol(h_copy, weights, sizeof(weights));④ 如果常量变量在多个.cu文件中共享需要在声明时使用extern关键字一个文件定义其他文件声明// file1.cu: 定义 __constant__ float shared_data[1024]; // file2.cu: 声明 extern __constant__ float shared_data[1024];注意常量内存的生命周期和程序一致程序结束常量内存才结束初始化一次后面都可以一直用了内存类型生命周期声明方式常量内存(__constant__)整个应用程序最长文件作用域全局声明全局内存(cudaMalloc)由程序员控制从分配cudaMalloc到释放cudaFree动态分配共享内存(__shared__)线程块Block的生命周期核函数内声明寄存器 / 局部内存线程的生命周期核函数内声明局部变量使用常量内存实现一维模板相信人工智能发达的今天大家都听过卷积或者上过视觉的或者数值分析等课程如果没听过的去查一下很简单就是一个卷积核模板一个矩阵卷积就是把模板依次滑过矩阵在对应位置相乘再相加这是数值分析里的一维高阶差分求导模板那这就是一维高阶求导d_in是你的数据然后我们经过模板之后每个位置都用相邻的8个元素包括本身就是9个元素然后进行差分求导求出来的数据就是d_out[]每个点都有导数通过以上的背景我们知道c0c1c2c3是常量是模板那么可以使用常量内存而且此时的输入数据d_in可以使用共享内存因为每个数据都要被使用8次所以最好的办法就是一次加载多次使用那就是共享内存#includecuda_runtime.h #include ../freshman.hpp using namespace std; #define TEMPLATE_SIZE 9 #define TEMP_RADIO_SIZE (TEMPLATE_SIZE/2) #define BDIM 32 __constant__ float coef[TEMP_RADIO_SIZE]; void convolution(float *in,float *out,float* template_,const unsigned int array_size) { for(int iTEMP_RADIO_SIZE;iarray_size-TEMP_RADIO_SIZE;i) { for(int j1;jTEMP_RADIO_SIZE;j) { out[i]template_[j-1]*(in[ij]-in[i-j]); } //printf(%d:CPU :%lf\n,i,out[i]); } } __global__ void stencil_1d(float * in,float * out){ //block边缘会越界为了不越界并且能够计算所以填充前面四个后面四个一共8个位置 __shared__ float smem[BDIM2*TEMP_RADIO_SIZE]; //计算全局索引 int idxthreadIdx.xblockDim.x*blockIdx.x; //跳过前面四个进行加载数据到共享内存 int sidxthreadIdx.xTEMP_RADIO_SIZE; smem[sidx]in[idx]; //此时处理前面四个和后面四个 if (threadIdx.xTEMP_RADIO_SIZE){ //如果此时的block是中间的那么前面和后面都有数据直接用 if(idxTEMP_RADIO_SIZE) smem[sidx-TEMP_RADIO_SIZE]in[idx-TEMP_RADIO_SIZE]; if(idxgridDim.x*blockDim.x-BDIM) smem[sidxBDIM]in[idxBDIM]; } //同步保证所有数据都加载到共享内存 __syncthreads(); //全局边缘位置不计算 if (idxTEMP_RADIO_SIZE||idxgridDim.x*blockDim.x-TEMP_RADIO_SIZE) return; float temp.0f; //计算差分 #pragma unroll for(int i1;iTEMP_RADIO_SIZE;i) { tempcoef[i-1]*(smem[sidxi]-smem[sidx-i]); } out[idx]temp; //printf(%d:GPU :%lf,\n,idx,temp); } //read only __global__ void stencil_1d_readonly(float * in,float * out,const float* __restrict__ dcoef) { __shared__ float smem[BDIM2*TEMP_RADIO_SIZE]; int idxthreadIdx.xblockDim.x*blockIdx.x; int sidxthreadIdx.xTEMP_RADIO_SIZE; smem[sidx]in[idx]; if (threadIdx.xTEMP_RADIO_SIZE) { if(idxTEMP_RADIO_SIZE) smem[sidx-TEMP_RADIO_SIZE]in[idx-TEMP_RADIO_SIZE]; if(idxgridDim.x*blockDim.x-BDIM) smem[sidxBDIM]in[idxBDIM]; } __syncthreads(); if (idxTEMP_RADIO_SIZE||idxgridDim.x*blockDim.x-TEMP_RADIO_SIZE) return; float temp.0f; #pragma unroll for(int i1;iTEMP_RADIO_SIZE;i) { tempdcoef[i-1]*(smem[sidxi]-smem[sidx-i]); } out[idx]temp; //printf(%d:GPU :%lf,\n,idx,temp); } int main(int argc,char** argv){ int dimxBDIM; unsigned int nxy116; int nBytesnxy*sizeof(float); //Malloc float* in_host(float*)malloc(nBytes); float* out_gpu(float*)malloc(nBytes); float* out_cpu(float*)malloc(nBytes); memset(out_cpu,0,nBytes); //initialData(in_host,nxy); //cudaMalloc float *in_devNULL; float *out_devNULL; initialData(in_host,nxy); float templ_[]{-1.0,-2.0,2.0,1.0}; CHECK(cudaMemcpyToSymbol(coef,templ_,TEMP_RADIO_SIZE*sizeof(float))); CHECK(cudaMalloc((void**)in_dev,nBytes)); CHECK(cudaMalloc((void**)out_dev,nBytes)); CHECK(cudaMemcpy(in_dev,in_host,nBytes,cudaMemcpyHostToDevice)); CHECK(cudaMemset(out_dev,0,nBytes)); // cpu compute double iStartefficiency(); convolution(in_host,out_cpu,templ_,nxy); double iElapsefficiency()-iStart; //printf(CPU Execution Time elapsed %f sec\n,iElaps); // stencil 1d dim3 block(dimx); dim3 grid((nxy-1)/block.x1); double stencil_1d_startefficiency(); stencil_1dgrid,block(in_dev,out_dev); CHECK(cudaDeviceSynchronize()); double stencil_1d_iElapsefficiency()-stencil_1d_start; printf(stencil_1d Time elapsed %f sec\n,stencil_1d_iElaps); CHECK(cudaMemcpy(out_gpu,out_dev,nBytes,cudaMemcpyDeviceToHost)); check(out_cpu,out_gpu,nxy); CHECK(cudaMemset(out_dev,0,nBytes)); // stencil 1d read only float * dcoef_ro; CHECK(cudaMalloc((void**)dcoef_ro,TEMP_RADIO_SIZE * sizeof(float))); CHECK(cudaMemcpy(dcoef_ro,templ_,TEMP_RADIO_SIZE * sizeof(float),cudaMemcpyHostToDevice)); double stencil_1d_readonly_startefficiency(); stencil_1d_readonlygrid,block(in_dev,out_dev,dcoef_ro); CHECK(cudaDeviceSynchronize()); double stencil_1d_readonly_iElapsefficiency()-stencil_1d_readonly_start; printf(stencil_1d_readonly Time elapsed %f sec\n,stencil_1d_readonly_iElaps); CHECK(cudaMemcpy(out_gpu,out_dev,nBytes,cudaMemcpyDeviceToHost)); check(out_cpu,out_gpu,nxy); cudaFree(dcoef_ro); cudaFree(in_dev); cudaFree(out_dev); free(out_gpu); free(out_cpu); free(in_host); cudaDeviceReset(); return 0; }只读缓存只读缓存在现代 Ada Lovelace 架构中并不是一个独立的物理硬件而是L1 数据缓存的一种“VIP 使用模式”。它的作用是当你明确告诉编译器“我这块数据在整个核函数运行期间都不会被修改”时L1 缓存会用更优的策略来缓存这些数据让它们尽量不被频繁读写的数据挤掉。分配方式方式一使用__ldg()内联函数最直接__ldg()是 CUDA 提供的一个内置函数专门用于发起一次只读的全局内存加载。你不需要改变指针的类型声明只需在读取数据时用__ldg()包裹一下即可。__global__ void kernel_readonly(float *output, float *large_table, int n) { int idx threadIdx.x blockIdx.x * blockDim.x; if (idx n) { // 用 __ldg() 显式告诉编译器这次加载是只读的请走只读缓存路径 float val __ldg(large_table[idx]); output[idx] val * 2.0f; } }关键点__ldg()作用于每次具体的加载操作你可以只对关键的只读数据使用它其他读写数据仍然走普通路径。方式二使用const __restrict__指针更省心在核函数的参数声明中给只读指针加上const __restrict__修饰。编译器会自动把符合条件的内存访问映射到只读缓存路径。// 注意参数中的 const __restrict__ __global__ void kernel_readonly(float *output, const float* __restrict__ large_table, int n) { int idx threadIdx.x blockIdx.x * blockDim.x; if (idx n) { // 不需要 __ldg()编译器自动走只读缓存路径 float val large_table[idx]; output[idx] val * 2.0f; } }关键点const表示数据不可修改__restrict__告诉编译器这个指针是访问这块内存的唯一途径没有别名编译器因此可以放心地把所有对large_table的读取都映射到只读缓存路径。实验//read only __global__ void stencil_1d_readonly(float * in,float * out,const float* __restrict__ dcoef) { __shared__ float smem[BDIM2*TEMP_RADIO_SIZE]; int idxthreadIdx.xblockDim.x*blockIdx.x; int sidxthreadIdx.xTEMP_RADIO_SIZE; smem[sidx]in[idx]; if (threadIdx.xTEMP_RADIO_SIZE) { if(idxTEMP_RADIO_SIZE) smem[sidx-TEMP_RADIO_SIZE]in[idx-TEMP_RADIO_SIZE]; if(idxgridDim.x*blockDim.x-BDIM) smem[sidxBDIM]in[idxBDIM]; } __syncthreads(); if (idxTEMP_RADIO_SIZE||idxgridDim.x*blockDim.x-TEMP_RADIO_SIZE) return; float temp.0f; #pragma unroll for(int i1;iTEMP_RADIO_SIZE;i) { tempdcoef[i-1]*(smem[sidxi]-smem[sidx-i]); } out[idx]temp; //printf(%d:GPU :%lf,\n,idx,temp); }两次的数据非常接近甚至有时候只读的还能跑过常量可能是跟数据量非常小的问题但是只读的波动非常大有时候还很慢总结特性常量内存 (Constant Memory)只读缓存 (Read-Only Cache)物理存储片外 DRAM但拥有一块独立的片上常量缓存Ada 架构为 8KB/SM数据仍在片外 DRAM加速路径是L1 数据缓存的一部分非独立硬件触发方式__constant__声明 cudaMemcpyToSymbol初始化__ldg()内联函数或const __restrict__指针核心优势单周期广播一个 Warp 内所有线程读同一地址时一个时钟周期完成缓存保护数据被标记为只读优先保留在 L1 缓存不被普通读写数据轻易挤掉最佳访问模式全 Warp 统一地址如所有线程读同一个权重每个线程读不同地址但这些地址通常是连续且只读的容量限制64KB整个 GPU受 L1 缓存容量和全局内存容量共同限制远大于 64KB主机端如何写入cudaMemcpyToSymbol专用 APIcudaMemcpy普通拷贝常量内存是专为“小、统一、只读”场景打造的广播利器只读缓存是为“大、各异、只读”场景提供的 L1 保护策略。

相关新闻

React ref机制详解:从原理到最佳实践

React ref机制详解:从原理到最佳实践

1. React中的ref机制深度解析在React开发中,ref是一个经常被使用却又容易被误解的特性。它本质上是一个"逃生舱",允许我们直接访问DOM节点或React组件实例。与state不同,ref的变动不会触发组件重新渲染,这使得它成为处理…

2026/8/4 6:24:34 阅读更多 →
C++实现带约束最大子段和:从算法竞赛题目解析到工程实践

C++实现带约束最大子段和:从算法竞赛题目解析到工程实践

1. 项目概述与核心需求解析看到“打卡信奥刷题(2146)用C实现信奥 P12190 [蓝桥杯 2025 省 Java C] 小说”这个标题,我第一反应是,这题目信息量不小,而且有点“跨界”的味道。它本质上是一个典型的算法竞赛题目&#xf…

2026/8/4 6:23:34 阅读更多 →
VMware CentOS虚拟机分辨率问题:open-vm-tools安装与配置全攻略

VMware CentOS虚拟机分辨率问题:open-vm-tools安装与配置全攻略

1. 项目概述:为什么虚拟机里的屏幕总是不听话?如果你也曾在VMware Workstation里安装CentOS,然后对着那个要么小得可怜、要么无法固定、一拖动窗口就变形的屏幕分辨率发愁,那你来对地方了。这几乎是每个刚接触Linux虚拟化环境的朋…

2026/8/4 6:23:34 阅读更多 →

最新新闻

终极抖音批量下载解决方案:专业级无水印视频采集架构深度解析

终极抖音批量下载解决方案:专业级无水印视频采集架构深度解析

终极抖音批量下载解决方案:专业级无水印视频采集架构深度解析 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallbac…

2026/8/4 12:03:05 阅读更多 →
如何让《明日方舟》干员成为你的桌面小伙伴?Ark-Pets桌宠实战指南

如何让《明日方舟》干员成为你的桌面小伙伴?Ark-Pets桌宠实战指南

如何让《明日方舟》干员成为你的桌面小伙伴?Ark-Pets桌宠实战指南 【免费下载链接】Ark-Pets Arknights Desktop Pets | 明日方舟桌宠 (ArkPets) 项目地址: https://gitcode.com/gh_mirrors/ar/Ark-Pets 想不想让《明日方舟》里的干员们从游戏里跳出来&#…

2026/8/4 12:03:05 阅读更多 →
【单片机毕业设计推荐】基于 STM32 的智能恒温出水控制系统设计与实现 基于 STM32 的蓝牙物联网饮水机监测控制系统设计(012105)

【单片机毕业设计推荐】基于 STM32 的智能恒温出水控制系统设计与实现 基于 STM32 的蓝牙物联网饮水机监测控制系统设计(012105)

文章目录20 个相关毕业设计备选题目项目研究背景摘要总体方案核心功能基础功能核心功能辅助功能技术路线项目演示关于我们项目案例源码获取温馨提示:本人主页置顶文章(点我)有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶…

2026/8/4 12:03:04 阅读更多 →
Java全栈面试宝典:从P6到P8-2

Java全栈面试宝典:从P6到P8-2

第2章:JVM深度解析(100题) 面试官视角:本章面试官最看重的3个点 内存模型和GC的实战理解——不是背"堆分新生代和老年代",而是能说清楚"什么对象进老年代、什么时候触发GC、GC日志怎么看"。我见过太多人能画出JVM内存结构图,但一问线上OOM怎么排查就…

2026/8/4 12:03:04 阅读更多 →
CSS3文本样式完全指南:从基础到高级特效

CSS3文本样式完全指南:从基础到高级特效

1. 为什么CSS3文本样式是前端入门的必修课?我刚入行前端时,曾经花了两周时间调整一个按钮的文字样式——间距不对齐、颜色不统一、阴影效果突兀。直到系统学习了CSS3文本属性,才发现原来三行代码就能解决的问题,我硬是折腾了14天。…

2026/8/4 12:03:04 阅读更多 →
如何用wxauto在3分钟内打造你的微信自动化机器人:终极完整指南

如何用wxauto在3分钟内打造你的微信自动化机器人:终极完整指南

如何用wxauto在3分钟内打造你的微信自动化机器人:终极完整指南 【免费下载链接】wxauto Windows版本微信客户端(非网页版)自动化,可实现简单的发送、接收微信消息,简单微信机器人 项目地址: https://gitcode.com/gh_…

2026/8/4 12:02:04 阅读更多 →

日新闻

AI Agent白手起家26: 使用标准事件驱动大模型实践

AI Agent白手起家26: 使用标准事件驱动大模型实践

纲要 练习目标:掌握大模型标准事件的调用回顾 LangChain 中的核心标准事件 invokestreambatchastream_eventswith_structured_output 环境准备实战代码:多种事件调用对比 同步调用与流式输出批量处理异步事件流监听结构化输出 运行说明与预期结果总结与扩…

2026/8/4 0:00:40 阅读更多 →
dealsea是什么?跨境卖家必知的美国deal站入门指南

dealsea是什么?跨境卖家必知的美国deal站入门指南

说实话,第一次听说美国这个老牌折扣网站的跨境卖家,十个有八个会问同一个问题:这个平台到底是干嘛的?我见过一个做家居出口的朋友,他在亚马逊上月销二十万美金,却从来没用过它。我给他看了首页——一屏一屏…

2026/8/4 0:01:40 阅读更多 →
清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

通讯作者:邓兵、刘建国通讯单位:清华大学DOI:https://doi.org/10.1021/acs.est.6c00603研究背景稀土元素(REEs)是清洁能源技术与电子器件不可或缺的核心原料,然而传统提取方式依赖能耗高、排放大的采矿与强…

2026/8/4 0:01:40 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/3 4:58:13 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/4 11:41:39 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/4 5:26:40 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/3 13:07:03 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/4 11:09:16 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/3 8:27:36 阅读更多 →