常量内存和只读缓存
目录摘要常量内存常量缓存特性分配方式使用常量内存实现一维模板只读缓存分配方式实验总结摘要本文将介绍常量内存和只读缓存然后对比其差异常量内存常量内存是一种专用的全局内存区域用于存放只读数据。对于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/10/16 2:43:05 阅读更多 →
C++实现带约束最大子段和:从算法竞赛题目解析到工程实践

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

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

2026/10/16 2:55:06 阅读更多 →
VMware CentOS虚拟机分辨率问题:open-vm-tools安装与配置全攻略

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

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

2026/10/16 2:55:31 阅读更多 →

最新新闻

Activiti核心表ACT_RE_PROCDEF深度解析:结构与排障实战

Activiti核心表ACT_RE_PROCDEF深度解析:结构与排障实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 1:07:16 阅读更多 →
std::map 完全指南:底层红黑树、API 陷阱与性能优化实战

std::map 完全指南:底层红黑树、API 陷阱与性能优化实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 1:07:16 阅读更多 →
Vue History模式打包后刷新404?Spring Boot与Nginx三种解决方案

Vue History模式打包后刷新404?Spring Boot与Nginx三种解决方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 1:07:16 阅读更多 →
前端面试必考:事件绑定、冒泡与代理机制深度解析

前端面试必考:事件绑定、冒泡与代理机制深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 1:07:16 阅读更多 →
Python实现Excel转SQLite:告别卡顿,高效处理海量数据

Python实现Excel转SQLite:告别卡顿,高效处理海量数据

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 1:07:16 阅读更多 →
MP2642GR-Z+STM32F415RG双串锂电主动均衡方案

MP2642GR-Z+STM32F415RG双串锂电主动均衡方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 1:06:16 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/15 19:41:51 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →