【CUDA学习笔记】第五篇:内存以及案例解释(附案例代码下载方式)(一)

简介: 【CUDA学习笔记】第五篇:内存以及案例解释(附案例代码下载方式)(一)

1、常量内存


  NVIDIA GPU卡从逻辑上对用户提供了64KB的常量内存空间,可以用来存储内核执行期间所需要的恒定数据。常量内存对一些特定情况下的小数据量的访问具有相比全局内存的额外优势。使用常量内存也一定程度上减少了对全局内存的带宽占用。


   话不多说,直接coding吧:

#include "stdio.h"
#include<iostream>
#include <cuda.h>
#include <cuda_runtime.h>
//使用__constant__定义两个常量constant_f,constant_g
__constant__ int constant_f;
__constant__ int constant_g;
#define N5
//定义设备的Kernel函数
__global__ void gpu_constant_memory(float *d_in, float *d_out) {
//线程的index
int tid = threadIdx.x;
d_out[tid] = constant_f * d_in[tid] + constant_g;
}
int main(void) {
//定义主机的数组
float h_in[N], h_out[N];
//定义设备指针
float *d_in, *d_out;
int h_f = 2;
int h_g = 20;
// 申请cpu内存
cudaMalloc((void**)&d_in, N * sizeof(float));
cudaMalloc((void**)&d_out, N * sizeof(float));
//初始化数组
for (int i = 0; i < N; i++) {
h_in[i] = i;
}
//把主机上的数组复制到设备上
cudaMemcpy(d_in, h_in, N * sizeof(float), cudaMemcpyHostToDevice);
//把定义的常量复制到GPU设备的常量内存中
cudaMemcpyToSymbol(constant_f, &h_f, sizeof(int), 0, cudaMemcpyHostToDevice);
cudaMemcpyToSymbol(constant_g, &h_g, sizeof(int));
//使用1个N个线程的Block调用和执行Kernel函数
gpu_constant_memory << <1, N >> > (d_in, d_out);
//把设备运算的结果传回CPU主机
cudaMemcpy(h_out, d_out, N * sizeof(float), cudaMemcpyDeviceToHost);
//打印结果
printf("Use of Constant memory on GPU \n");
for (int i = 0; i < N; i++) {
printf("The expression for input %f is %f\n", h_in[i], h_out[i]);
}
//释放GPU设备内存
cudaFree(d_in);
cudaFree(d_out);
return 0;
}

image.png

   常量内存中的变量使用__constant__关键字修饰。在之前的代码中,两个浮点数constant_f,constant_g被定义成在内核执行期间不会改变的常量。同时使用__constant__(在内核外面)定义好了它们后,它们不应该再次在内核内部定义。


   在main函数中,h_f,h_g两个常量在主机上被定义并初始化,然后将被复制到设备上的常量内存中。

   使用cudaMemcpyToSymbol函数把这些常量复制到内核执行所需要的常量内存中。

   该函数具有五个参数:

第一个参数:是(要写入的)目标,也就是我们刚才用__constant__定义过的h_f或者h_g常量;

第二个参数:是源主机地址;

第三个参数:是传输大小;

第四个参数:是写入目标的偏移量,这里是0;

第五个参数:是设备到主机的数据传输方向;

 最后两个参数是可选的,因此后面第二次cudaMemcpyToSymbol函数调用的时候省略掉了它们。


2、纹理内存


   纹理内存是另外一种当数据的访问具有特定的模式的时候能够加速程序执行,并减少显存带宽的只读存储器。像常量内存一样,它也在芯片内部被cache缓冲。该存储器最初是为了图形绘制而设计的,但也可以被用于通用计算。当程序进行具有很大程度上的空间邻近性的访存的时候,这种存储器变得非常高效。


   空间邻近性的意思是,每个线程的读取位置都和其他线程的读取位置邻近。这对那些需要处理4个邻近的相关点或者8个邻近的点的图像处理应用非常有用。一种线程进行2D的平面空间邻近性的访存的例子如下表:

image.png

   通用的全局内存的cache将不能有效处理这种空间邻近性,可能会导致进行大量的显存读取传输。纹理存储被设计成能够利用这种访存模型,这样它只会从显存读取1次,然后缓冲掉,所以执行速度将会快得多。纹理内存支持2D和3D的纹理读取操作,在你的CUDA程序里面使用纹理内存可没有那么轻易,特别是对那些并非编程专家的人来说。

#include "stdio.h"
#include<iostream>
#include <cuda.h>
#include <cuda_runtime.h>
#define NUM_THREADS 10
#define N 10
//通过【纹理引用】来定义一段能进行纹理拾取的纹理内存
texture <float, 1, cudaReadModeElementType> textureRef;
__global__ void gpu_texture_memory(int n, float *d_out)
{
int idx = blockIdx.x*blockDim.x + threadIdx.x;
if (idx < n) {
float temp = tex1D(textureRef, float(idx));
d_out[idx] = temp;
}
}
int main()
{
//计算Block的个数,用于后面的使用
int num_blocks = N / NUM_THREADS + ((N % NUM_THREADS) ? 1 : 0);
//声明设备的指针
float *d_out;
// 在设备上为结果申请显存
cudaMalloc((void**)&d_out, sizeof(float) * N);
// 在主机设备上为结果申请内存
float *h_out = (float*)malloc(sizeof(float)*N);
//声明并初始化主机的数组
float h_in[N];
for (int i = 0; i < N; i++) {
h_in[i] = float(i);
}
//定义GPU设备的数组
cudaArray *cu_Array;
cudaMallocArray(&cu_Array, &textureRef.channelDesc, N, 1);
//把数据复制到GPU的数组中
cudaMemcpyToArray(cu_Array, 0, 0, h_in, sizeof(float)*N, cudaMemcpyHostToDevice);
// GPU数组绑定前面申请的纹理内存
cudaBindTextureToArray(textureRef, cu_Array);
//调用Kernel函数,并运算
gpu_texture_memory << <num_blocks, NUM_THREADS >> > (N, d_out);
// 把结果复制回主机以方便打印和处理
cudaMemcpy(h_out, d_out, sizeof(float)*N, cudaMemcpyDeviceToHost);
printf("Use of Texture memory on GPU: \n");
for (int i = 0; i < N; i++) {
printf("Texture element at %d is : %f\n", i, h_out[i]);
}
free(h_out);
cudaFree(d_out);
cudaFreeArray(cu_Array);
cudaUnbindTexture(textureRef);
}

   纹理引用是通过texture<>类型的变量进行定义的。定义的时候,它具有3个参数:


第一个参数:texture<>类型的变量定义时候的参数,用来说明纹理元素的类型。在本例中,是float类型;

第二个参数:说明了纹理引用的类型,可以是1D的,2D的,3D的。在本例中,是1D的纹理引用;

第三个参数:则是读取模式,这是一个可选参数,用来说明是否要执行读取时候的自动类型转换。


   CUDA数组的使用与普通的数组类似,但是它却是纹理专用的。CUDA数组对于内核函数来说是只读的。但可以在主机上通过cudaMemcpyToArray函数写入,如同之前的代码中看到的那样。在cudaMemcpyToArray函数中,第二个和第三个参数中的0代表传输到的目标CUDA数组横向和纵向上的偏移量。两个方向上的偏移量都是0代表我们的这次传输将从目标CUDA数组的左上角(0,0)开始。CUDA数组中的存储器布局对用户来说是不透明的,这种布局对纹理拾取进行过特别优化。


   cudaBindTextureToArray函数,将纹理引用和CUDA数组进行绑定。之前写入内容的CUDA数组将成为该纹理引用的后备存储。纹理引用绑定完成后调用内核,该内核将进行纹理拾取,同时将结果数据写入到显存中的目标数组。


注意:CUDA对于显存中常见的大数据量的存储方式有两种:    

   一种:是普通的线性存储,可以直接用指针访问。

   另外一种:则是CUDA数组,对用户不透明,不能在内核里直接用指针访问,需要通过texture或者surface的相应函数进行访问。

   本例的内核中,从texture reference进行的读取使用了相应的纹理拾取函数,而写入直接用普通的指针(d_out[])进行。当内核执行完成后,结果数组被复制回到主机上的内存中,然后在控制台窗口中显示出来。当使用完纹理存储后,我们需要执行解除绑定的代码,这是通过调用cudaUnbindTexture函数进行的。然后使用cudaFreeArray()函数释放刚才分配的CUDA数组空间。


注意:请一定要确保纹理引用被定义成全局静态变量,同时还要确保它不能作为参数传递给任何其他函数。在这个内核函数中,每个线程通过纹理引用读取自己线程ID作为索引位置的数据,然后复制到d_out指针指向的全局内存中。

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
26天前
|
存储 缓存 监控
Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
本文介绍了Docker容器性能调优的关键技巧,涵盖CPU、内存、网络及磁盘I/O的优化策略,结合实战案例,旨在帮助读者有效提升Docker容器的性能与稳定性。
67 7
|
26天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
50 5
|
26天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
59 1
|
1月前
|
存储 JavaScript 前端开发
如何优化代码以避免闭包引起的内存泄露
本文介绍了闭包引起内存泄露的原因,并提供了几种优化代码的策略,帮助开发者有效避免内存泄露问题,提升应用性能。
|
2月前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
3月前
|
存储 并行计算 算法
CUDA统一内存:简化GPU编程的内存管理
在GPU编程中,内存管理是关键挑战之一。NVIDIA CUDA 6.0引入了统一内存,简化了CPU与GPU之间的数据传输。统一内存允许在单个地址空间内分配可被两者访问的内存,自动迁移数据,从而简化内存管理、提高性能并增强代码可扩展性。本文将详细介绍统一内存的工作原理、优势及其使用方法,帮助开发者更高效地开发CUDA应用程序。
|
3月前
|
NoSQL 程序员 Linux
轻踩一下就崩溃吗——踩内存案例分析
踩内存问题分析成本较高,尤其是低概率问题困难更大。本文详细分析并还原了两个由于动态库全局符号介入机制(it's a feature, not a bug)触发的踩内存案例。
|
4月前
|
存储 缓存 JSON
一行代码,我优化掉了1G内存占用
这里一行代码,指的是:String.intern()的调用,为了调用这一行代码,也写了几十行额外的代码。
|
4月前
|
缓存 Java
Java内存管理秘籍:掌握强软弱幻四大引用,让代码效率翻倍!
【8月更文挑战第29天】在Java中,引用是连接对象与内存的桥梁,主要分为强引用、软引用、弱引用和幻象引用。强引用确保对象生命周期由引用控制,适用于普通对象;软引用在内存不足时可被回收,适合用于内存敏感的缓存;弱引用在无强引用时即可被回收,适用于弱关联如监听器列表;幻象引用需与引用队列配合使用,用于跟踪对象回收状态,适用于执行清理工作。合理使用不同类型的引用车可以提升程序性能和资源管理效率。
49 4
|
4月前
|
前端开发 JavaScript Java
揭开 JavaScript 垃圾回收的秘密——一场与内存泄漏的生死较量,让你的代码从此焕然一新!
【8月更文挑战第23天】本文通过多个实例深入探讨了JavaScript中的垃圾回收机制及其对应用性能的影响。首先介绍了基本的内存管理方式,随后分析了变量不再使用时的回收过程。接着,通过事件监听器未被移除及全局变量管理不当等场景展示了常见的内存泄漏问题。最后,文章介绍了使用`WeakRef`和`FinalizationRegistry`等现代API来有效避免内存泄漏的方法。理解并运用这些技术能显著提升Web应用的稳定性和效率。
99 0

热门文章

最新文章