CUDA内存分配

https://blog.csdn.net/runlong_86/article/details/33732125

内存分配详解 

http://blog.163.com/liulijuan_llj/blog/static/177843275201153102339612/
c/c++编程基础篇之浅析堆&栈
五大内存分区
在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。
栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。
堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。
常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改

一般认为在c中分为这几个存储区:
1. 栈 --有编译器自动分配释放 
2. 堆 -- 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 
3. 全局区(静态区) -- 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束释放。 
4. 另外还有一个专门放常量的地方。程序结束释放 
在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。另外,函数中的"adgfdf"这样的字符串存放在常量区。 
比如:
代码: 
int a = 0; //全局初始化区 
char *p1; //全局未初始化区 
main() 

int b; //栈 
char s[] = "abc"; //栈 
char *p2; //栈 
char *p3 = "123456"; //123456\0在常量区,p3在栈上。 
static int c = 0; //全局(静态)初始化区 
p1 = (char *)malloc(10); 
p2 = (char *)malloc(20); 
//分配得来得10和20字节的区域就在堆区。 
strcpy(p1, "123456"
//123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一块。 

http://www.cnblogs.com/traceorigin/archive/2013/04/11/3015554.html

概述:线性存储器可以通过cudaMalloc()、cudaMallocPitch()和cudaMalloc3D()分配

1、1D线性内存分配

1 cudaMalloc(void**,int)    //在设备端分配内存
2 cudaMemcpy(void* dest,void* source,int size,enum direction)    //数据拷贝
3 cudaMemcpyToSymbol       //将数据复制到__constant__变量中,或者__device__变量中
4 cudaMemcpyFromSynbol   //同上相反
5 cudaFree()               //内存释放
6 cudaMemset()           //内存初始化

注意:主机和设备间的数据交换会自动同步,而设备与设备却不会,需要使用cudaThreadSynchronize()

2、2D线性内存分配

2.1 分配

1 cudaMallocPitch( void** devPtr,size_t* pitch,size_t widthInBytes,size_t height ) //在线性内存中分配二维数组,width的单位是字节,而height单位是数据类型

c语言申请2维内存时,一般是连续存放的。a[y][x]存放在第y*widthofx*sizeof(元素)+x*sizeof(元素)个字节。

在cuda的global memory访问中,从256字节对齐的地址(addr=0, 256, 512, ...)开始的连续访问是最有效率的。这样,为了提高内存访问的效率,有了cudaMallocPitch函数。cudaMallocPitch函数分配的内存中,数组的每一行的第一个元素的开始地址都保证是对齐的。因为每行有多少个数据是不确定的,widthofx*sizeof(元素)不一定是256的倍数。故此,为保证数组的每一行的第一个元素的开始地址对齐,cudaMallocPitch在分配内存时,每行会多分配一些字节,以保证widthofx*sizeof(元素)+多分配的字节是256的倍数(对齐)。这样,上面的y*widthofx*sizeof(元素)+x*sizeof(元素)来计算a[y][x]的地址就不正确了。而应该是y*[widthofx*sizeof(元素)+多分配的字节]+x*sizeof(元素)。而函数中返回的pitch的值就是widthofx*sizeof(元素)+多分配的字节。说明:widthInBytes作为输入参数,应该是widthofx*sizeof(元素);这样的话,复制内容时也要作相应的修改。

2.2 访问

1 T* pElement = (T*)((char*)BaseAddress + Row * pitch) + Column;           //元素访问方式

cudaMallocPitch()以*pitch的形式返回间距,即所分配存储器的宽度,以字节为单位。间距用作存储器分配的一个独立参数,用于在2D数组内计算地址。

2.3 拷贝

1 cudaMemcpy2D( void* dst,size_t dpitch,const void* src,size_t spitch,size_t width,size_t height,enum cudaMemcpyKind kind )

这里需要特别注意width与pitch的区别,width是实际需要拷贝的数据宽度而pitch是2D线性存储空间分配时对齐的行宽,而当数据传递发生在设备与主机之间时,主机端pitch==width.

综上我们可以看到,CUDA下对二维线性空间的访问是不提供多下标支持的,访问时依然是通过计算偏移量得到,不同的地方在于使用pitch对齐后非常利于实现coalesce访问

例:下面的代码分配了一个尺寸为width*height的二维浮点数组,同时演示了怎样在设备代码中遍历数组元素

 1 // Host code
 2   int width = 64, height = 64;
 3   float* devPtr;
 4   int pitch;
 5   cudaMallocPitch((void**)&devPtr, &pitch, width * sizeof(float), height); 
 6   MyKernel<<<100, 512>>>(devPtr, pitch, width, height);
 7 // Device code
 8   __global__ void MyKernel(float* devPtr, int pitch, int width, int height){
 9    for (int r = 0; r < height; ++r) {
10       float* row = (float*)((char*)devPtr + r * pitch);
11       for (int c = 0; c < width; ++c) {
12          float element = row[c];
13       }
14    }
15 }

 

3、3D线性内存

1 cudaError_t cudaMalloc3D(    
2     struct cudaPitchedPtr *     pitchedDevPtr,
3     struct cudaExtent             extent     
4 )    

例:下面的代码分配了一个尺寸为width*height*depth的三维浮点数组,同时演示了怎样在设备代码中遍历数组元素

 1 // Host code
 2 cudaPitchedPtr devPitchedPtr;
 3 cudaExtent extent = make_cudaExtent(64, 64, 64);
 4 cudaMalloc3D(&devPitchedPtr, extent);  
 5 MyKernel<<<100, 512>>>(devPitchedPtr, extent);
 6 // Device code
 7 __global__ void MyKernel(cudaPitchedPtr devPitchedPtr, cudaExtent extent) {
 8    char* devPtr = devPitchedPtr.ptr;
 9    size_t pitch = devPitchedPtr.pitch;
10    size_t slicePitch = pitch * extent.height;
11    for (int z = 0; z < extent.depth; ++z) {
12      char* slice = devPtr + z * slicePitch;
13      for (int y = 0; y < extent.height; ++y) {
14         float* row = (float*)(slice + y * pitch); 
15         for (int x = 0; x < extent.width; ++x) { float element = row[x];
16      }
17    }
18 }
谈谈显存的使用分析
http://blog.csdn.net/shi06/article/details/5265463
在基本完成了对nvcc的使用分析解决了实验室的CUDA环境统一问题后,我转向解决存储器传输问题。看起来这个部分没有设计算法那样光纤,但作为实验室GPU组的组长脏活累活得自己干,抱怨一下下!其实也不算是,看起来参考指南上已经说的很清楚了,我主要就是去了解实现细节和测试性能,但是尽信书不如无书,而且没有自己的性能分析数据,就无法确切的知道,这个CUDA程序的“沉没成本”到底有多大!
其实了解CUDA程序设计的,都知道CUDA程序与传统C语言程序在运行时一个较大的区别就是多了一个数据在主机和设备间传输交换的环节,因此我们在设计CUDA并行程序时除了要设计合理的高性能并行算法外,有效利用存储器带宽也是一个重要的性能优化手段!
一、本专题内容简介
CUDA的存储器主要分为片外和片内两大类,片内存储器的带宽优化对程序员来讲主要是考虑如何有效使用share memory,这个内容将在share memory研究专题中给出技术分析;而片外存储器主要有global memory,local memory,constant memory以及texture memory!其中local memory是非常低带宽的存储器,虽然它是编译器分配的,但在设计kernel时可利用一些技巧尽可能的避免使用它,constant memory和texture memory的性能特性分析也将包含在其它技术专题中,本文只会用texture memory和global memory进行一些对比,而不会对其进行深入探讨。
在我们实验室的系统开发中重点考虑的数据传输是host to device的传输时间,那么首先看下两端各有些什么。在device端主要有线性存储器和专为纹理的CUDA array,在host端主要有分页内存和页锁定内存,在CUDA2.2以上页锁定内存还新增3种具有特殊功能的存储器使用方式:用于主机多线程的portable,用于CPU写的write-combined以及零拷贝的mapping,但我们实验室目前的设备不支持mapping所以不会做探讨,另外一个用途就是支持异步传输掩盖延迟。
本专题的内容包括对线性存储器的使用说明和带宽分析,以2D global memory为例的原因在于对齐后的线性存储空间非常易于实现合并访问,这对访存有数量级的提升。显存分配和传输时间的分析包括了主机端分页内存到1D,2D线性存储器和CUDA array以及页锁定到2D线性存储器等类型。在此基础上对比分析了texture memory和global memory的访存性能。最后是研究以异步的方式实现数据传输的延时掩盖。

二、内存与显存的数据传输类型与使用说明
(1)主机端分页内存的使用方式
对于C语言,主机端的分页内存是用calloc或者malloc函数进行一维空间的分配。而对于二维空间的分配则可以使用下面的函数进行分配:
float **get_matrix_float(int m,int n)
{
int i;
float **a;

a=(float **)calloc(m,sizeof(float *));
for(i=0;i<m;i++)a[i]=(float *)calloc(n,sizeof(float));
 return a;
}
分页内存空间的释放采用free即可,二维的情况则用循环释放。
(2)主机端页锁定内存的使用方式
在CUDA2.2以下,仅提供cudaMallocHost函数用于分配页锁定内存,与C语言函数malloc分配分页内存相对应。而从CUDA2.2开始,页锁定内存增加三种新的类型用于主机多线程的portable,用于高效写回write-combined以及零拷贝的mapping,用cudaHostAlloc进行分配,其中采用四个可选参数标志用以指定使用何种特性:
• cudaHostAllocDefault: 默认情况等价于cudaMallocHost().
• cudaHostAllocPortable: 用于主机多线程数据共享的portable方式
• cudaHostAllocMapped: 通过映射方式分配给CUDA地址空间实现zero-copy,在设备上使用的指针通过调用cudaHostGetDevicePointer()获取
• cudaHostAllocWriteCombined: 分配写联合的存储空间,主要用于主机到设备的传输或者通过映射页锁定空间CPU写而设备读的情况。但CPU读的效率不高。
函数cudaFreeHost()用于释放页锁定空间。
(3)设备端线性存储器的使用说明
虽然CUDA的显存分配函数包括1D,2D和3D的形式,但均不支持多下标访问。对于1D线性空间采用cudaMalloc进行分配和cudaMemcpy进行数据拷贝,其使用方式与分页内存的方式基本一致,当然添加了数据拷贝的方向的控制。还有个需要注意的地方主机和设备间数据交换会自动同步,而设备与设备间不会,需要使用cudaThreadSynchronize()。
但对于2D和3D则不同,以2D为例,分配的函数为cudaMallocPitch,由于它不支持双下标寻址也不支持二级指针,其实就是cudaMalloc的对齐形式,但数据访问方式有大的改变,须采用标准访问形式,即:
T* pElement = (T*)((char*)BaseAddress + Row * pitch) + Column;
注意指针BaseAddress仍为一级指针,本人测试过若将其声明为二级指针,按道理采用如下访问方式:
T* pElement = (T*)((char*)*BaseAddress + Row * pitch) + Column
此方式在模拟条件下能得到正确结果,但实际设备上无法得到正确输出,这也表征了CUDA的线性存储器本质上与内存的不同。
cudaMemcpy2D是用于2D线性存储器的数据拷贝,函数原型为:
cudaMemcpy2D( void* dst,size_t dpitch,const void* src,size_t spitch,size_t width,size_t height,enum cudaMemcpyKind kind )
这里需要特别注意width与pitch的区别,width是实际需要拷贝的数据宽度而pitch是2D线性存储空间分配时对齐的行宽,而当数据传递发生在设备与主机之间时,主机端pitch==width。
综上我们可以看到,CUDA下对二维线性空间的访问是不提供多下标支持的,访问时依然是通过计算偏移量得到,不同的地方在于使用pitch对齐后非常利于实现coalesce访问。
(4)设备端CUDA array的使用方式
CUDA array是专为纹理获取使用的,是不能通过其它方式进行访问的,其分配函数包括cudaMallocArray和cudaMalloc3DArray,这里以2D纹理使用为例,给出一个使用范例。
二维纹理声明:texture tex;
纹理通道声明:cudaChannelFormatDesc channelDesc = cudaCreateChannelDesc();
CUDA Array的分配与数据拷贝:
cudaMallocArray( &d_array, &channelDesc, width, length ); 
cudaMemcpyToArray( d_array, 0, 0, d_image, h_size*sizeof(int), cudaMemcpyDeviceToDevice);
纹理参数设置:
tex.addressMode[0] = cudaAddressModeClamp;
tex.addressMode[1] = cudaAddressModeClamp;
tex.filterMode = cudaFilterModePoint;
tex.normalized = false;
纹理绑定:cudaBindTextureToArray(tex, d_array, channelDesc);
二维纹理拾取(未归一化):tex2D(tex, x, y);
解除纹理绑定:cudaUnbindTexture(tex);

猜你喜欢

转载自blog.csdn.net/chengde6896383/article/details/81023653