GPU编程--Shared Memory(4)

GPU的内存按照所属对象大致分为三类:线程独有的、block共享的、全局共享的。细分的话,包含global, local, shared, constant, and texture memoey, 我们重点关注以下两类内存

  • Global memory

Global memory resides in device memory and device memory is accessed via 32-, 64-, or 128-bytes memory transactions

  • Shared memory

Because it is on-chip, shared memory has much higher bandwidth and much lower latency than local or global memory

简单理解就是,Shared memory更快。以下是内存按照所属对象分类示意图

有了对Global memory、Shared memory的印象之后,我们通过矩阵相乘的例子要谈谈这两种内存的运用,并对比他们的优劣(老规矩,先代码,后解释)

// Matrices are stored in row-major order:
// M(row, col) = *(M.elements + row * M.width + col)
typedef struct {
  int width;
  int height;
  float* elements;
} Matrix;
// Thread block size
#define BLOCK_SIZE 16
// Forward declaration of the matrix multiplication kernel
__global__ void MatMulKernel(const Matrix, const Matrix, Matrix);
// Matrix multiplication - Host code
// Matrix dimensions are assumed to be multiples of BLOCK_SIZE
void MatMul(const Matrix A, const Matrix B, Matrix C)
{
  // Load A and B to device memory
  Matrix d_A;
  d_A.width = A.width; d_A.height = A.height;
  size_t size = A.width * A.height * sizeof(float);
  cudaMalloc(&d_A.elements, size);
  cudaMemcpy(d_A.elements, A.elements, size,
  cudaMemcpyHostToDevice);
  Matrix d_B;
  d_B.width = B.width; d_B.height = B.height;
  size = B.width * B.height * sizeof(float);
  cudaMalloc(&d_B.elements, size);
  cudaMemcpy(d_B.elements, B.elements, size,
  cudaMemcpyHostToDevice);
  // Allocate C in device memory
  Matrix d_C;
  d_C.width = C.width; d_C.height = C.height;
  size = C.width * C.height * sizeof(float);
  cudaMalloc(&d_C.elements, size);
  // Invoke kernel
  dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);
  dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y);
  MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C);
  // Read C from device memory
  cudaMemcpy(C.elements, Cd.elements, size,
  cudaMemcpyDeviceToHost);
  // Free device memory
  cudaFree(d_A.elements);
  cudaFree(d_B.elements);
  cudaFree(d_C.elements);
}
// Matrix multiplication kernel called by MatMul()
__global__ void MatMulKernel(Matrix A, Matrix B, Matrix C)
{
  // Each thread computes one element of C
  // by accumulating results into Cvalue
  float Cvalue = 0;
  int row = blockIdx.y * blockDim.y + threadIdx.y;
  int col = blockIdx.x * blockDim.x + threadIdx.x;
  for (int e = 0; e < A.width; ++e)
    Cvalue += A.elements[row * A.width + e]
      * B.elements[e * B.width + col];
  C.elements[row * C.width + col] = Cvalue;}

计算原理如下

host端代码很常规,我们重点关注__global__标记的这个device端代码,她完成的功能很简单就是去A矩阵的一行、B矩阵的一列。行列对应元素相乘累加,也就是向量的点击运算。当运算结束的时候矩阵C=AB。这是很常规的一种思路。

那么,如何用Shared memory完成上述功能呢?这样的好处又是什么呢?(老规矩,先代码,后解释)

// Matrices are stored in row-major order:
// M(row, col) = *(M.elements + row * M.stride + col)
typedef struct {
  int width;
  int height;
  int stride;
  float* elements;
} Matrix;
// Get a matrix element
__device__ float GetElement(const Matrix A, int row, int col)
{
  return A.elements[row * A.stride + col];
}
// Set a matrix element
__device__ void SetElement(Matrix A, int row, int col,
  float value)
{
  A.elements[row * A.stride + col] = value;
}
// Get the BLOCK_SIZExBLOCK_SIZE sub-matrix Asub of A that is
// located col sub-matrices to the right and row sub-matrices down
// from the upper-left corner of A
__device__ Matrix GetSubMatrix(Matrix A, int row, int col)
{
  Matrix Asub;
  Asub.width = BLOCK_SIZE;
  Asub.height = BLOCK_SIZE;
  Asub.stride = A.stride;
  Asub.elements = &A.elements[A.stride * BLOCK_SIZE * row
    + BLOCK_SIZE * col];
  return Asub;
}
// Thread block size
#define BLOCK_SIZE 16
// Forward declaration of the matrix multiplication kernel
__global__ void MatMulKernel(const Matrix, const Matrix, Matrix);
// Matrix multiplication - Host code
// Matrix dimensions are assumed to be multiples of BLOCK_SIZE
void MatMul(const Matrix A, const Matrix B, Matrix C)
{
  // Load A and B to device memory
  Matrix d_A;
  d_A.width = d_A.stride = A.width; d_A.height = A.height;
  size_t size = A.width * A.height * sizeof(float);
  cudaMalloc(&d_A.elements, size);
  cudaMemcpy(d_A.elements, A.elements, size,
  cudaMemcpyHostToDevice);
  Matrix d_B;
  d_B.width = d_B.stride = B.width; d_B.height = B.height;
  size = B.width * B.height * sizeof(float);
  cudaMalloc(&d_B.elements, size);
  cudaMemcpy(d_B.elements, B.elements, size,
  cudaMemcpyHostToDevice);
  // Allocate C in device memory
  Matrix d_C;
  d_C.width = d_C.stride = C.width; d_C.height = C.height;
  size = C.width * C.height * sizeof(float);
  cudaMalloc(&d_C.elements, size);
  // Invoke kernel
  dim3 dimBlock(BLOCK_SIZE, BLOCK_SIZE);
  dim3 dimGrid(B.width / dimBlock.x, A.height / dimBlock.y);
  MatMulKernel<<<dimGrid, dimBlock>>>(d_A, d_B, d_C);
  // Read C from device memory
  cudaMemcpy(C.elements, d_C.elements, size,
  cudaMemcpyDeviceToHost);
  // Free device memory
  cudaFree(d_A.elements);
  cudaFree(d_B.elements);
  cudaFree(d_C.elements);
}
// Matrix multiplication kernel called by MatMul()
__global__ void MatMulKernel(Matrix A, Matrix B, Matrix C)
{
  // Block row and column
  int blockRow = blockIdx.y;
  int blockCol = blockIdx.x;
  // Each thread block computes one sub-matrix Csub of C
  Matrix Csub = GetSubMatrix(C, blockRow, blockCol);
  // Each thread computes one element of Csub
  // by accumulating results into Cvalue
  float Cvalue = 0;
  // Thread row and column within Csub
  int row = threadIdx.y;
  int col = threadIdx.x;
  // Loop over all the sub-matrices of A and B that are
  // required to compute Csub
  // Multiply each pair of sub-matrices together
  // and accumulate the results
  for (int m = 0; m < (A.width / BLOCK_SIZE); ++m) {
  // Get sub-matrix Asub of A
    Matrix Asub = GetSubMatrix(A, blockRow, m);
  // Get sub-matrix Bsub of B
  Matrix Bsub = GetSubMatrix(B, m, blockCol);
  // Shared memory used to store Asub and Bsub respectively
  __shared__ float As[BLOCK_SIZE][BLOCK_SIZE];
  __shared__ float Bs[BLOCK_SIZE][BLOCK_SIZE];
  // Load Asub and Bsub from device memory to shared memory
  // Each thread loads one element of each sub-matrix
  As[row][col] = GetElement(Asub, row, col);
  Bs[row][col] = GetElement(Bsub, row, col);
  // Synchronize to make sure the sub-matrices are loaded
  // before starting the computation
  __syncthreads();

  // Multiply Asub and Bsub together
  for (int e = 0; e < BLOCK_SIZE; ++e)
    Cvalue += As[row][e] * Bs[e][col];
  // Synchronize to make sure that the preceding
  // computation is done before loading two new
  // sub-matrices of A and B in the next iteration
  __syncthreads();
  }
  // Write Csub to device memory
  // Each thread writes one element
  SetElement(Csub, row, col, Cvalue);}

计算原理如下

__device__标记的函数只能由__device__、__global__标记的函数调用。GetElement函数就是得到矩阵A(row,col)这一坐标上的值,SetElement函数就是将矩阵A(row,col)的值设置为value。GetSubMatrix函数就是得到矩阵A的子矩阵,用matlab的语法表示就是Asub=A[row:row+BLOCK_SIZE,col:col+BLOCK_SIZE]。

host端代码还是很常规的,下面重点分析__global__标记的函数。这个函数是以block为单位组织的,她首先获取矩阵C的一个子矩阵Csub,然后用该block内的线程ID索引Csub矩阵的所有元素。每一次for循环,获取A的子矩阵Asub、B的子矩阵Bsub(请参考上述示意图)。然后将Asub、Bsub的有global memory搬迁到shared memory。__syncthreads()的作用是,等所有的线程都将数据搬迁完了,再向下执行。之后的一个for循环完成的功能是Asub、Bsub对应元素向量点击运算。沿A的宽度方向、B的高度方向迭代,即可完成Csub内所有点的向量点击运算。

总结:引入shared memory的好处可以概括为“不要把时间浪费在路上,尤其是路途遥远的路上”。将Global memory的数据搬迁到thread比较费时。

时间: 2024-10-06 17:39:33

GPU编程--Shared Memory(4)的相关文章

GPU 编程 global memory 的使用

最近做作业,发现了一个一直不理解的问题终于明白了,高兴! block只是用来划分task. block和device memory 访问没有对应关系. block可以访问任一device memory的内容. 之前,以为block只能访问划分给它的那块数据!! 比如以前提过的两矩阵乘法, 矩阵小块可以除了访问该矩阵小块对应的device memory,还访问了同一列分区和同一行分区的 device memory. 但是,block在写device memory的时候,就需要注意了! 总之: bl

cuda编程:关于共享内存(shared memory)和存储体(bank)的事实和疑惑

关于共享内存(shared memory)和存储体(bank)的事实和疑惑 主要是在研究访问共享内存会产生bank conflict时,自己产生的疑惑.对于这点疑惑,网上都没有相关描述, 不管是国内还是国外的网上资料.貌似大家都是当作一个事实,一个公理,而没有对其仔细研究.还是我自己才学疏浅,不知道某些知识. 比如下面这篇讲解bank conflict的文章. http://cuda-programming.blogspot.com/2013/02/bank-conflicts-in-share

GPU 编程入门到精通(五)之 GPU 程序优化进阶

博主因为工作其中的须要,開始学习 GPU 上面的编程,主要涉及到的是基于 GPU 的深度学习方面的知识.鉴于之前没有接触过 GPU 编程.因此在这里特地学习一下 GPU 上面的编程. 有志同道合的小伙伴,欢迎一起交流和学习.我的邮箱: [email protected] .使用的是自己的老古董笔记本上面的 Geforce 103m 显卡,尽管显卡相对于如今主流的系列已经很的弱,可是对于学习来说.还是能够用的.本系列博文也遵从由简单到复杂,记录自己学习的过程. 0. 文件夹 GPU 编程入门到精通

CUDA ---- Shared Memory

CUDA SHARED MEMORY shared memory在之前的博文有些介绍,这部分会专门讲解其内容.在global Memory部分,数据对齐和连续是很重要的话题,当使用L1的时候,对齐问题可以忽略,但是非连续的获取内存依然会降低性能.依赖于算法本质,某些情况下,非连续访问是不可避免的.使用shared memory是另一种提高性能的方式. GPU上的memory有两种: · On-board memory · On-chip memory global memory就是一块很大的on

C++: Virtual Table and Shared Memory

See at:补充栏3: C++对象和共享内存 (叙述内容和Link1的内容基本一致) <C++网络编程 卷1:运用ACE和模式消除复杂性> <C++ Network Programming Volume 1 Mastering Complexity with ACE and Patterns> -Douglas C.Schmidt, Stephen D. Huston -叶斌译 Link1: Is it possible to store polymorphic class in

Shared Memory共享内存----kithara RTS

翻译注:共享内存是程序之间进行数据交互的最基本的方式,而由于windows和kithara rts本身为两个独立的系统,为了能够使两个不同系统之上的程序进行通信,那么就必须开辟一块内存区域用于数据共享.本文是对kithara rts官方原文进行的翻译,加入了本人的一些使用经验. Shared Memory(共享内存) What you need to know about Shared Memory(共享内存基本知识) In 32-bit and64-bit Windows operating

CUDA学习(五)之使用共享内存(shared memory)进行归约求和

共享内存(shared memory)是位于SM上的on-chip(片上)一块内存,每个SM都有,就是内存比较小,早期的GPU只有16K(16384),现在生产的GPU一般都是48K(49152). 共享内存由于是片上内存,因而带宽高,延迟小(较全局内存而言),合理使用共享内存对程序效率具有很大提升. 下面是使用共享内存对一个数组进行求和,使用全局内存进行归约求和可以浏览https://www.cnblogs.com/xiaoxiaoyibu/p/11397205.html #pragma on

CUDA学习(六)之使用共享内存(shared memory)进行归约求和(M个包含N个线程的线程块)

在https://www.cnblogs.com/xiaoxiaoyibu/p/11402607.html中介绍了使用一个包含N个线程的线程块和共享内存进行数组归约求和, 基本思路: 定义M个包含N个线程的线程块时(NThreadX = ((NX + ThreadX - 1) / ThreadX)),全局线程索引需使用tid = blockIdx.x * blockDim.x + threadIdx.x,而在每个线程块中局部线程索引是i = threadIdx.x, 每个线程块只计算一部分求和,

GPU编程中的常用数学函数

在GPU编程中,函数一般分为以下几种类型:数学函数.几何函数.纹理映射函数.偏导数函数.调试函数等.熟练利用好GPU自带函数,可以在一定程度上提高并行编程速度与效率. 关于数学数学函数(Mathematical Functions) 数学函数用于执行数学上常用计算,比如:三角函数.幂函数.向量和矩阵函数,这些函数一般都被重载,用来支持标量数据和不同长度的向量作为输入参数.列表如下: 标准函数库中的数学函数 未完待续......