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 = ;
  int row = blockIdx.y * blockDim.y + threadIdx.y;
  int col = blockIdx.x * blockDim.x + threadIdx.x;
  for (int e = ; 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 = ;
  // 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 = ; 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 = ; 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比较费时。

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

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

    博主因为工作其中的须要,開始学习 GPU 上面的编程,主要涉及到的是基于 GPU 的深度学习方面的知识.鉴于之前没有接触过 GPU 编程.因此在这里特地学习一下 GPU 上面的编程. 有志同道合的小伙 ...

  2. CUDA ---- Shared Memory

    CUDA SHARED MEMORY shared memory在之前的博文有些介绍,这部分会专门讲解其内容.在global Memory部分,数据对齐和连续是很重要的话题,当使用L1的时候,对齐问题 ...

  3. GPU编程自学6 —— 函数与变量类型限定符

    深度学习的兴起,使得多线程以及GPU编程逐渐成为算法工程师无法规避的问题.这里主要记录自己的GPU自学历程. 目录 <GPU编程自学1 -- 引言> <GPU编程自学2 -- CUD ...

  4. 【并行计算-CUDA开发】CUDA shared memory bank 冲突

    CUDA SHARED MEMORY shared memory在之前的博文有些介绍,这部分会专门讲解其内容.在global Memory部分,数据对齐和连续是很重要的话题,当使用L1的时候,对齐问题 ...

  5. GPU编程和流式多处理器

    GPU编程和流式多处理器 流式多处理器(SM)是运行CUDA内核的GPU的一部分.本章重点介绍SM的指令集功能. 流式多处理器(SM)是运行我们的CUDA内核的GPU的一部分.每个SM包含以下内容. ...

  6. C++: Virtual Table and Shared Memory

    See at: 补充栏3: C++对象和共享内存 (叙述内容和Link1的内容基本一致) <C++网络编程 卷1:运用ACE和模式消除复杂性> <C++ Network Progra ...

  7. GPU编程自学4 —— CUDA核函数运行参数

    深度学习的兴起,使得多线程以及GPU编程逐渐成为算法工程师无法规避的问题.这里主要记录自己的GPU自学历程. 目录 <GPU编程自学1 -- 引言> <GPU编程自学2 -- CUD ...

  8. GPU 编程相关 简要摘录

    GPU 编程可以称为异构编程,最近由于机器学习的火热,很多模型越来越依赖于GPU来进行加速运算,所以异构计算的位置越来越重要:异构编程,主要是指CPU+GPU或者CPU+其他设备(FPGA等)协同计算 ...

  9. 使用shared memory 计算矩阵乘法 (其实并没有加速多少)

    #include "cuda_runtime.h" #include "device_launch_parameters.h" #include "d ...

随机推荐

  1. 通过VMware安装Linux操作系统

    1.安装好vmvare,下载好Linux,本文采用redhat 6.5  (下载linux可参考http://www.linuxidc.com/Linux/2007-09/7399.htm) 2.选择 ...

  2. JavaEE开发之SpringMVC中的自定义拦截器及异常处理

    上篇博客我们聊了<JavaEE开发之SpringMVC中的路由配置及参数传递详解>,本篇博客我们就聊一下自定义拦截器的实现.以及使用ModelAndView对象将Controller的值加 ...

  3. Extjs6(二)——用extjs6.0写一个系统登录及注销

    本文基于ext-6.0.0 一.写login页 1.在view文件夹中创建login文件夹,在login中创建文件login.js和loginController.js(login.js放在class ...

  4. CF #356 div1 A. Bear and Prime 100

    题目链接:http://codeforces.com/contest/679/problem/A CF有史以来第一次出现交互式的题目,大致意思为选择2到100中某一个数字作为隐藏数,你可以询问最多20 ...

  5. PROFINET如何实现实时性

    平时我们都听过文艺作品要“源于生活而高于生活”.PROFINET是基于工业以太网的,用文艺范儿的词汇说就是“源于以太网而高于以太网”.那么,PROFINET是怎么做到“高于以太网”的呢? 要做到比普通 ...

  6. Java 8 lambda初试

    λ表达式本质上是一个匿名方法.让我们来看下面这个例子: public int add(int x, int y) { return x + y; } 转成λ表达式后是这个样子: (int x, int ...

  7. windows下编译java源文件的编码错误

    import java.util.Arrays;public class ArrayAsAReference{ public static void main(String[] args) { int ...

  8. Vue2.0的变化 ,组件模板,生命周期,循环,自定义键盘指令,过滤器

    组件模板: 之前: <template> <h3>我是组件</h3><strong>我是加粗标签</strong> </templat ...

  9. 使用DocFX生成文档

    使用DocFX命令行生成文档 使用docfx 命令 1.下载 https://github.com/dotnet/docfx/releases 2.使用 创建初始项目 docfx init -q 此命 ...

  10. Java学习笔记——浅谈数据结构与Java集合框架(第三篇、Map)

    桃李春风一杯酒,江湖夜雨十年灯 --寄黄几复 之前图上写错了,是Hashtable类.t是小写的,它是个很古老的类,以至于命名都没有那么规范.. HashMap HashMap就是存储key-valu ...