title: 【CUDA 基础】5.2 共享内存的数据布局

categories:

- CUDA

- Freshman

tags:

- 行主序

- 列主序

toc: true

date: 2018-06-02 21:01:03



Abstract: 本文主要研究几个关于共享内存的例子,以此来了解共享内存的性质,为我们的核函数加速

Keywords: 行主序,列主序,填充与无填充,从线程索引体映射数据元素

开篇废话

同一个东西,A花大工夫做到极致,成本100,售价200;C模仿A的做法快速的通过仿制,节省了研发试验的所有开销,但是没有做到A那么完美,成本25,售价140。A虽然好但是不见得销量有C高,并且A的利润并没有C那么高,所以,作为商人,选择C是没错的,商人的目的就是盈利,但是问题来了,如果不是商人呢?而是一个科学家呢?

本文我们主要研究共享内存的数据布局,通过代码实现,来观察运行数据,换句话说,我们主要研究上一篇中的放西瓜,取西瓜,以及放冬瓜等的一些列操作对性能的影响,以及如何才能使效率最大化。

几个例子包括以下几个主题:

  • 方阵与矩阵数组
  • 行主序与列主序
  • 静态与动态共享内存的声明
  • 文件范围与内核范围的共享内存
  • 内存填充与无内存填充

当使用共享内存设计核函数的时候下面两个概念是非常重要的:

  1. 跨内存存储体映射数据元素
  2. 从线程索引到共享内存偏移的映射

当上面这些主题和概念都得到很好地理解,设计一个高效的使用共享内存的核函数就没什么问题了,其可以避免存储体冲突并充分利用共享内存的优势。

注意,从几何上讲,方形属于矩形,这里我们说的矩形时指长方形。

方形共享内存

我们前面说过我们的线程块可以是一维二维和三维的,对应的线程编号是threadIdx.x, threadIdx.y以及threadIdx.z,为了对应一个二维的共享内存,我们假设我们使用二维的线程块,那么对于一个二维的共享内存

#define N 32
...
__shared__ int x[N][N];
...

当我们使用二维块的时候,很有可能会使用下面这种方式来索引x的数据:

#define N 32
...
__shared__ int x[N][N];
...
int a=x[threadIdx.y][threadIdx.x];

当然这个索引就是 (y,x)(y,x)(y,x) 对应的,我们也可以用 (x,y)(x,y)(x,y) 来索引。

在CPU中,如果用循环遍历二维数组,尤其是双层循环的方式,我们倾向于内层循环对应x,因为这样的访问方式在内存中是连续的,因为CPU的内存是线性存储的,但是GPU的共享内存并不是线性的,而是二维的,分成不同存储体的,并且,并行也不是循环,那么这时候,问题完全不同,没有任何可比性。

回顾放西瓜的例子以及存储体冲突的特性,容易想到,我们最应该避免的是存储体冲突,那么对应的问题就来了,我们每次执行一个线程束,对于二维线程块,一个线程束是按什么划分的呢?是按照threadIdx.x 维进行划分还是按照threadIdx.y维进行划分的呢?

这句话有点迷糊?那我再啰嗦一遍,因为这个很关键,我们每次执行的是一个线程束,线程束里面有很多线程,对于一个二维的块,切割线程束有两种方法,顺着y切,那么就是threadIdx.x固定(变化慢),而threadIdx.y是连续的变化,顺着x切相反;CUDA明确的告诉你,我们是顺着x切的,也就是一个线程束中的threadIdx.x 连续变化。

我们的数据是按照行放进存储体中的这是固定的,所以我们希望,这个线程束中取数据是按照行来进行的,所以

x[threadIdx.y][threadIdx.x];

这种访问方式是最优的,threadIdx.x在线程束中体现为连续变化的,而对应到共享内存中也是遍历共享内存的同一行的不同列

上面这个确实有点绕,我们可以画画图,多想象一下CUDA的运行原理,这个就好理解了,说白了就是不要一个线程束中访问一列共享内存,而是要访问一行。

对照上图,我们把一个int类型(四字节)的1024个元素的数组放到共享内存A中,每个int的索引对应到蓝框中,假设我们的块大小是 (32,32)(32,32)(32,32) 那么我们第一个线程束就是 threadIdx.y=0,threadIdx.x=0…31,如果我们使用

A[threadIdx.x][threadIdx.y];

的索引方式,就会得到绿框的数据,可想而知,这冲突达到了最大,效率最低、

果我们使用

A[threadIdx.y][threadIdx.x];

我们就会得到红色框中的数据,无冲突,一个事务完成。

本文全部代码在GitHub上可下载使用:https://github.com/Tony-Tan/CUDA_Freshman

行主序访问和列主序访问

行主序访问和列主序访问我们上面已经把原理基本介绍清楚了,我们下面看实现后的试验,这里我们研究的访问,包括读和写,也就是加载和存储。

我们定义块的尺寸为

#define BDIMX 32
#define BDIMY 32

核函数只完成简单的两个操作:

  • 将全局线程索引值存入二维共享内存
  • 从共享内存中按照行主序读取这些值并存到全局内存中

项目完整的代码在24_shared_memory_read_data这个文件夹下,下文我们只贴部分代码。

核函数如下

__global__ void setRowReadRow(int * out)
{
__shared__ int tile[BDIMY][BDIMX];
unsigned int idx=threadIdx.y*blockDim.x+threadIdx.x; tile[threadIdx.y][threadIdx.x]=idx;
__syncthreads();
out[idx]=tile[threadIdx.y][threadIdx.x];
}
  • 定义一个共享内存,大小为 32×3232\times 3232×32
  • 计算当前线程的全局位置的值idx
  • 将idx这个无符号整数值写入二维共享内存tile[threadIdx.y][threadIdx.x]中
  • 同步
  • 将共享内存tile[threadIdx.y][threadIdx.x]中的值写入全局内存对应的idx位置处

核函数的内存工作:

  1. 共享内存的写入
  2. 共享内存的读取
  3. 全局内存的写入

这个核函数按照行主序读和写,所以对于共享内存没有读写冲突

另一种方法就是按照列主序访问了,核函数代码如下:

__global__ void setColReadCol(int * out)
{
__shared__ int tile[BDIMY][BDIMX];
unsigned int idx=threadIdx.y*blockDim.x+threadIdx.x; tile[threadIdx.x][threadIdx.y]=idx;
__syncthreads();
out[idx]=tile[threadIdx.x][threadIdx.y];
}

原理不再赘述,我们直接看运行结果:

对于使用nvprof如果出现 ======== Error: unified memory profiling failed.错误,是因为系统的保护机制,所以使用sudo权限来执行即可,如果sudo找不到你的nvprof,你可以用完整路径,或则添加到环境变量:

可见行主序的平均时间是 1.552μs1.552\mu s1.552μs 而列主序是 2.4640μs2.4640\mu s2.4640μs 注意如果直接使用来方法即cpu计时,那么会非常不准,比如我们红色方框内就是cpu计时的结果,原因是数据量太小,运行时间太短,误差相对就太大了,这显然是错误,很有可能我们前面也出现过理论和实际不符的情况也是因为计时有问题。

接下来我们看看检测存储体冲突的指标,会是什么数据:

shared_load_transactions_per_request
shared_store_transactions_per_request
  • shared_load_transactions_per_request 结果:
nvprof  --metrics shared_load_transactions_per_request ./shared_memory_read_data



可以看到load过程行主序1个事务,而列主序32个

  • shared_store_transactions_per_request 结果:
nvprof  --metrics shared_store_transactions_per_request ./shared_memory_read_data

同样行主序的事务是1,而列主序的事务是32

注意,我们这个设备是4-byte宽的,上面第二张图中有相关信息。

按行主序写和按列主序读

完整内容在 https://face2ai.com/CUDA-F-5-2-共享内存的数据布局/

【CUDA 基础】5.2 共享内存的数据布局的更多相关文章

  1. 【CUDA 基础】4.1 内存模型概述

    title: [CUDA 基础]4.1 内存模型概述 categories: - CUDA - Freshman tags: - CUDA内存模型 - CUDA内存层次结构 - 寄存器 - 共享内存 ...

  2. 【CUDA 基础】4.2 内存管理

    title: [CUDA 基础]4.2 内存管理 categories: - CUDA - Freshman tags: - CUDA内存管理 - CUDA内存分配和释放 - CUDA内存传输 - 固 ...

  3. 【CUDA 基础】4.3 内存访问模式

    title: [CUDA 基础]4.3 内存访问模式 categories: - CUDA - Freshman tags: - 内存访问模式 - 对齐 - 合并 - 缓存 - 结构体数组 - 数组结 ...

  4. 【并行计算-CUDA开发】关于共享内存(shared memory)和存储体(bank)的事实和疑惑

    关于共享内存(shared memory)和存储体(bank)的事实和疑惑 主要是在研究访问共享内存会产生bank conflict时,自己产生的疑惑.对于这点疑惑,网上都没有相关描述, 不管是国内还 ...

  5. 【网络编程基础】Linux下进程通信方式(共享内存,管道,消息队列,Socket)

    在网络课程中,有讲到Socket编程,对于tcp讲解的环节,为了加深理解,自己写了Linux下进程Socket通信,在学习的过程中,又接触到了其它的几种方式.记录一下. 管道通信(匿名,有名) 管道通 ...

  6. CUDA基础介绍

    一.GPU简介 1985年8月20日ATi公司成立,同年10月ATi使用ASIC技术开发出了第一款图形芯片和图形卡,1992年4月ATi发布了Mach32图形卡集成了图形加速功能,1998年4月ATi ...

  7. C扩展 从共享内存shm到memcache外部内存

    引言 - ipc - shm 共享内存 本文会通过案例了解ipc 的共享内存机制使用, 后面会讲解C 如何使用外部内存服务memcached. 好先开始了解 linux 共享内存机制. 推荐先参看下面 ...

  8. Linux进程间通信(消息队列/信号量+共享内存)

    写在前面 不得不说,Deadline果真是第一生产力.不过做出来的东西真的是不堪入目,于是又花了一早上重写代码. 实验内容 进程通信的邮箱方式由操作系统提供形如 send()和 receive()的系 ...

  9. Linux 共享内存详解一

    共享内存段被多个进程附加的时候,如果不是所有进程都已经调用shmdt,那么删除该共享内存段时,会出现一个临时的不完整的共享内存段(key值是0),无法彻底删除.只有当所有进程都调用shmdt,这个临时 ...

随机推荐

  1. 【Python基础】13_Python中的PASS

    pass关键字的使用 在程序分支中,如果不想立刻执行该分支,可使用pass占位符,pass不表示任何含义,仅保证程序不会报错. 如: action_str = input("请选择希望执行的 ...

  2. python中requests库使用方法详解

    目录 python中requests库使用方法详解 官方文档 什么是Requests 安装Requests库 基本的GET请求 带参数的GET请求 解析json 添加headers 基本POST请求 ...

  3. ReLeQ:一种自动强化学习的神经网络深度量化方法

    ReLeQ:一种自动强化学习的神经网络深度量化方法     ReLeQ:一种自动强化学习的神经网络深度量化方法ReLeQ: An Automatic Reinforcement Learning Ap ...

  4. Rikka with Competition hdu 6095

    签到题目,排序然后按序清理掉一定会输的结果就可以. ac代码: #include <iostream> #include <cstdio> #include <cstri ...

  5. MongoDB的复合唯一索引

    一 创建 JavaScript Shell db.room.ensureIndex({'floor':1,'num':1}) Spring Data @Data // lombok @Document ...

  6. 微信小程序实现折叠面板

    wxml: <view class='help'> <view class='help_item'> <view class='title' data-index='1' ...

  7. Viola–Jones object detection framework--Rapid Object Detection using a Boosted Cascade of Simple Features中文翻译 及 matlab实现(见文末链接)

    ACCEPTED CONFERENCE ON COMPUTER VISION AND PATTERN RECOGNITION 2001 Rapid Object Detection using a B ...

  8. js重点——作用域——简单介绍(一)

    一.作用域 定义:在js中,作用域为变量,对象,函数可访问的一个范围. 分类:全局作用域和局部作用域 全局作用域:全局代表了整个文档document,变量或者函数在函数外面声明,那它的就是全局变量和全 ...

  9. ES6入门六:class的基本语法、继承、私有与静态属性、修饰器

    基本语法 继承 私有属性与方法.静态属性与方法 修饰器(Decorator) 一.基本语法 class Grammar{ constructor(name,age){ //定义对象自身的方法和属性 t ...

  10. bootstrap-selectpicker 插件事件

    $('#id').on('show.bs.select', function (e) { //绑定下拉显示列表触发事件 }); $('#id').on('hidden.bs.select', func ...