【CUDA】三、存储器层级结构及共享存储器

一、存储器简介


        对于CUDA线程,主要设置的存储器有:局部存储器、共享存储器、全局存储器、只读存储器。

        局部存储器:作用域在每个线程的内部,每个线程都有一个自己私有的存储器;

        共享存储器:作用域在线程块内,相同块内的线程可访问该存储中的数据,已达到数据共享;

        全局存储器:比共享存储器的作用域更大,所有块中的线程都可用访问该存储器中的数据

        只读存储器:包括常量存储器和纹理存储器

其中,全局存储器、只读存储器(常量存储器、文理存储器)的生命周期同当前应用程序的当前内核发射器一致!以下是相关存储器的层级结构:


二、共享存储器

        我们可在共享存储器中声明需要共享的变量,用__shared__限定符,可以和__device__限定符一起使用(可选),一个声明为__shared__的变量有以下特点:

        一、所属的作用域为当前线程块,也即块内的所有线程都都可访问这一限定符所声明的变量,从而起到了块内线程间的数据共享和通信作用;

        二、生命周期和线程保持一致;

        用法:

                 1、__shared__ float array[10];

                       声明了一个大小为10的float数组,块内的线程都共享数据,另外,该数据的大小是固定的,为10

                 2、 extern __shared__ float shared[];

                      声明了一个动态的数据,其大小并没有在此指定,通过执行配置的第三个参数Ns指定(表明共享存储器的空间大小,单位字节)

    

三、利用共享存储器进行矩阵乘法

        除了上面提到的,用__shared__声明的变量可在块内线程间起到通信作用和数据共享外,另外一个作用就是可降低对全局存储器的访问频率,从而节省带宽,当然得存在这样的情况:对全局存储器访问频繁,且能够用共享存储器去代替全局存储器。

        这里以共享存储器为基础,进行了矩阵的乘法运算,这里进行的是矩阵的分块计算,其算法为:对矩阵进行分块为n*n的大小,每个线程块负责计算一个块大小的数据,而具体到每个线程就只计算对应的数据即可。如下图计算C = A *B


            如果不对矩阵进行分块,就是简单的计算,对于计算矩阵C中的每个元素,都要向矩阵A中读取A.width个元素、向矩阵B中读取B.height个元素,如果每个线程负责计算一个对应的数据,由于并发,在计算期间对全局存储器的访问是多么的繁忙,每个线程都要读取A.width + B.height个元素!如果分块计算,那么对于计算一个块内数据,需要向矩阵A读取B.width / BLOCK_SIZE次,向矩阵B读取A.height / BLOCK_SIZE次。通过增加次数来降低对全局存储器的访问频率,节省带宽。

    

四、代码实现

#include <iostream>
#include <cuda_runtime.h>
#include <cstdlib>
#include <cassert>

using namespace std;

//下面定义矩阵分块的信息,用于计算矩阵C,C = A * B
//注意为了处理简单设置A和B的行数和列数相等

const int g_blockSize = 16;		//定义每块的大小
const int g_numBlock = 100;		//块数
const int g_matrixSize = g_blockSize * g_numBlock;	//矩阵的大小

struct Matrix
{
	int width;			//矩阵宽度
	int height;			//矩阵高度
	int stride;			//每行的步长,方便操作子矩阵,非子矩阵:width = stride
	float * pElements;	//数据元素的首地址
};

//创建一个矩阵matrix,其大小为size*size的方阵
//随机数初始化
void InitHostMatrix(Matrix & matrix,int size)
{
	matrix.width = size;
	matrix.height = size;
	matrix.stride = size;
	matrix.pElements = new float[matrix.stride * matrix.height];
	int n = matrix.stride * matrix.height;
	for (int i = 0; i < n; ++i)
	{
		matrix.pElements[i]= 1.f * rand() / RAND_MAX;
	}
}

//销毁一个host矩阵
void DestroyHostMatrix(Matrix & matrix)
{
	delete[] matrix.pElements;
	matrix.pElements = nullptr;
}

//用一个host矩阵创初始化device矩阵
//参数copyContent决定了是否将host矩阵的内容拷贝到device矩阵中,defalt:true
void InitDeviceMatrix(Matrix& deviceMatrix, const Matrix& hostMatrix,bool copyContent = true)
{
	assert(hostMatrix.width > 0 && hostMatrix.height > 0 && hostMatrix.stride && hostMatrix.pElements);
	deviceMatrix.width = hostMatrix.width;
	deviceMatrix.height = hostMatrix.height;
	deviceMatrix.stride = hostMatrix.stride;

	int totalSize = sizeof(float) * hostMatrix.stride * hostMatrix.height;
	cudaError err = cudaMalloc(&deviceMatrix.pElements, totalSize);
	if (err != cudaSuccess)
	{
		std::cerr << "call cudaMalloc fail!" << std::endl;
	}

	if (copyContent)
	{
		err = cudaMemcpy(deviceMatrix.pElements, hostMatrix.pElements, totalSize, cudaMemcpyHostToDevice);
		if (err != cudaSuccess)
		{
			std::cerr << "call cudaMemcpy fail!" << std::endl;
		}
	}
}

//销毁一个Device矩阵
void DestroyDeviceMatrix(Matrix & deviceMatrix)
{
	if (deviceMatrix.pElements)
	{
		cudaFree(deviceMatrix.pElements);
		deviceMatrix.pElements = nullptr;
	}
}

//获取矩阵的子矩阵,根据blockSize来划分块
//声明为__device,表明该函数由设备负责调用
//这里的row、col单位为块,并不是以具体的元素为单位
__device__ Matrix GetSubMatrix(const Matrix matrix, int row, int col)
{
	Matrix resultMatrix;
	resultMatrix.width = g_blockSize;
	resultMatrix.height = g_blockSize;
	resultMatrix.stride = matrix.stride;	//!!!这里设置为父矩阵的步长,以便用GetElement函数能够获取到正确的元素
	resultMatrix.pElements = matrix.pElements + row * g_blockSize * matrix.stride + col * g_blockSize;
	return resultMatrix;
}

//获取矩阵中指定行、列的元素
//这里row、col单位为元素个数,不为块
__device__ float GetElement(const Matrix matrix,int row,int col)
{
	return *(matrix.pElements + matrix.stride * row + col);
}

//设置矩阵指定行、列的元素
__device__ void SetElment(Matrix matrix, int row, int col,float value)
{
	*(matrix.pElements + matrix.stride * row + col) = value;
}


//计算matC,matC = matA * matB
//每个线程负责计算matC中的一个元素,唯一
__global__ void MatMulKernel(const Matrix matA, const Matrix matB, Matrix matC)
{
	//当前计算矩阵块编号
	const int blockRow = blockIdx.y;	//注意x、y是要颠倒的
	const int blockCol = blockIdx.x;

	const int threadRow = threadIdx.y;	
	const int threadCol = threadIdx.x;

	//定义共享子矩阵,每个线程负责从matC中复制数据
	__shared__ float sharedSubA[g_blockSize][g_blockSize];		
	__shared__ float sharedSubB[g_blockSize][g_blockSize];

	Matrix subC = GetSubMatrix(matC, blockRow, blockCol);

	float result = 0.f;
	
	//矩阵matA、matB都被分成了g_numBlock*g_numBlock块
	for (int b = 0; b < g_numBlock; ++b)
	{
		//1、获取当前块的数据
		//根据当前块索引,获取matA、matB对应的子矩阵,并将其内容复制到共享存储器中(每个线程只复制一个数据元素)
		Matrix subA = GetSubMatrix(matA, blockRow, blockCol);
		Matrix subB = GetSubMatrix(matB, blockRow, blockCol);
		//将数据复制到共享存储器中
		sharedSubA[threadRow][threadCol] = GetElement(subA, threadRow, threadCol);
		sharedSubB[threadRow][threadCol] = GetElement(subB, threadRow, threadCol);
		//同步线程
		__syncthreads();
		
		//2、计算
		for (int e = 0; e < g_blockSize; ++e)
		{
			result += sharedSubA[threadRow][e] * sharedSubB[e][threadCol];	//累计结果
		}

		__syncthreads();

	}

	//将结果写入矩阵C对应的子矩阵中
	SetElment(subC, threadRow, threadCol, result);
}


int main()
{
	//定义主机上的矩阵
	Matrix A, B, C;
	InitHostMatrix(A,g_matrixSize);		//初始化host矩阵,行列分别为g_matrixSize
	InitHostMatrix(B,g_matrixSize);
	InitHostMatrix(C,g_matrixSize);

	Matrix dA, dB, dC;
	InitDeviceMatrix(dA, A);
	InitDeviceMatrix(dB, B);
	InitDeviceMatrix(dC, C, false);		//不拷贝C的内容到dC中

	dim3 gridSize(g_numBlock, g_numBlock);		//这里预先指定了g_matrixSize = g_numBlock * g_blockSize
	dim3 blockSize(g_blockSize, g_blockSize);
	MatMulKernel << <gridSize, blockSize>> >(dA, dB, dC);
	//MatMulKernel << <gridSize, blockSize, sizeof(float)*g_blockSize * g_blockSize >> >(dA, dB, dC);

	cudaError err = cudaGetLastError();
	if (err != cudaSuccess)
	{
		std::cerr << "call MatMulKernel!" << std::endl;
	}

	//将计算结构从device拷贝到host
	err = cudaMemcpy(C.pElements, dC.pElements, sizeof(float)*dC.stride * dC.height, cudaMemcpyDeviceToHost);
	if (err != cudaSuccess)
	{
		std::cerr << "call cudaMemcpy fail!" << std::endl;
	}



	//销毁矩阵
	DestroyHostMatrix(A);
	DestroyHostMatrix(B);
	DestroyHostMatrix(C);

	DestroyDeviceMatrix(dA);
	DestroyDeviceMatrix(dB);
	DestroyDeviceMatrix(dC);


	cin.get();
	return 0;
}





  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值