本小节笔记大纲:

  • 1.Communication patterns

    • gather,scatter,stencil,transpose
  • 2.GPU hardware & Programming Model
    • SMs,threads,blocks,ordering
    • Synchronization
    • Memory model: local, shared, global
    • Atomic Operation
  • 3.Efficient GPU Programming
    • Access memory faster

      • coalescing global memory
      • use faster memory
    • Avoid thread divergence

一、Communication Patterns

1.Patterns

  • Map

map很好理解,其实就是映射,也就是输入和输出一一对应,一个萝卜一个坑

  • Gather

Gather中文名为收集,是将若干个输入数据经过计算后得到一个输出值,如图左示。很典型的应用就是比如说对于一个图像,我们需要每一个像素值是其四周像素的平均值。

  • Scatter

    scatter的特点是每个线程一次会向内存输出多个值,也可能多个线程向一个内存输出值。

  • Stencil

    Stencil表示模板的意思,所以也就是计算的时候用模子来选择输入数据,看下图就清楚了

  • Transpose

    其实就是转置啦~

具体应用实例如下:

在C语言中,加入我们定义了如上图示的一个结构体,包含float和int两种变量,然后我们又定义了一个该结构体的变量数组,一般来说其在内存中是像上面那样排列的,强迫症看起来是不是不舒服,而且这种排列方式比较浪费空间,所以通过转置后形成下面的排列方式后既美观又使运算加速了,岂不美哉?

2.练习题

  • 第一个很简单就是map,不仔细解释了
  • 第二个个表达式我之前脑袋一热就选了C。。但是要注意,scatter的特点是每个线程一次会向内存输出多个值,这显然不符合该特点,而应该是Transpose。
  • 第三个就是scatter了,原因如上
  • 最后一个很容易选stencil,但是你要注意if条件语句的限制,所以应该是Gather。

3.总结神图

二、GPU Hardware

1.问题导向

  • 线程是如何有效地一致访问内存

    • 子话题:如何利用数据重用
  • 线程如何通过共享内存通信部分结果

2.硬件组成

如图示,GPU由若干个SM(Stream Multiprocessor流多处理器)组成,而每个SM又包含若干个SP(教材上是Stream Processor流处理器,改视频中是simple processor),anyway...开心就好,管他叫什么名字~

GPU的作用是负责分配线程块在硬件SM上运行,所有SM都以并行独立的方式运行。

下面做一下题目吧:

解析:

  • 1正确.一个线程块包含许多线程
  • 2正确.一个SM可能会运行多个多个线程块
  • 3错误,因为一个线程块无法在一个以上的SM上运行
  • 4正确,在一个线程块上所有线程有可能配合起来解决某个子问题
  • 5错误,一个SM上可能有多个线程块,但是根据定义,线程和不同的线程块不应该存在协作关系。

3.程序员与GPU分工

另外需要注意的是程序员负责定义线程块,而GPU则负责管理硬件,因此程序员不能指定线程块的执行顺序,也不能指定线程块在某一特定的SM上运行。

这样设计的好处如下:

  • 硬件可以运行的更加有效率
  • 运行切换不需要等待,一旦一个线程块运行完毕,SM可以自动的将另一个线程块加载进来
  • 最大的优势:可扩展性,因为可以自动分配硬件资源,所以向下到单个SM,上到超级计算机的大量SM,均可以很好的适应。

有如上好处的同时,自然也就有局限性:

  • 对于哪个块在哪个SM上运行无法进行任何假设
  • 无法获得块之间的明确的通信

4.GPU Memory Model

如图示

  • 每个线程都有它自己的本地内存(local memory)
  • 线程块有一个共享内存(shared memory),块中所有线程都可以访问该内存中的数据
  • GPU中的全局内存(global memory) 是所有线程块中的线程都能访问的内存,也是CPU进行数据传递的地方。

访问速度:

local memory > shared memory > global

例题:

解析:

s,t,u是本地内存中的变量,所以t=s最先运行,同理可以排除其他代码运行顺序。

注意:这只是为了说明访问速度出的例题,实际情况中,编译器可能会做出相应的调整来达到我们的目的

5.Sychronization

说道线程,很自然我们就需要考虑同步。GPU中的同步有如下几种:

Barrier(屏障)

顾名思义,就是所有线程运行到这个点都需要停下来。

如图示,红色、蓝色、绿色代表的线程先后到达barrier这个时间点后都停下来进行同步操作,完成之后线程的执行顺序是不一定的,可能如图示蓝色线程先执行,绿色,红色紧随其后。

另外其实还有一种隐式的barrier,比如说先后启动kernel A和kernel B,一般来说kernel B执行之前kernel A肯定是执行完毕了的。

说了这么多来做下题吧~233

题目:如下图示,现在需要实现一个数组前移的操作,即后面一个往前面挪,共享数组大小是128,问为实现这个功能,需要设置几次同步操作(或者说需要设置几个barrier?)

解析:

最开始的时候没想明白,写了127,128,但是都不对。后来听解释才明白。前移操作可以分为三步:

  1. 为每个数组元素赋值,即
array[idx] = threadIdx.x;
__syncthreads(); # 128个线程都执行完赋值语句后才能进行下一步
  1. 读取后面一个元素的值,存在临时变量里
int temp = array[idx+1]; 
__syncthreads(); 
  1. 将后一元素的值往前移
array[idx] = temp; 
__syncthreads(); 

6.Atomic Memory Operation

在cuda编程中经常会碰到这样的情况,即大量的线程同时都需要对某一个内存地址进行读写操作,很自然这会发生冲突,如下图示:

下面是发生冲突的具体的代码示例:

#include "cuda_runtime.h" 
#include "device_launch_parameters.h" 
#define NUM_THREADS 10000
#define ARRAY_SIZE 10
#define BLOCK_WIDTH 100 void printDevice();  __global__ void increment_naive(int *g) { 
int i = blockIdx.x * blockDim.x + threadIdx.x;
i = i % ARRAY_SIZE;
g[i] = g[i] + 1; 
}  int main(int argc, char **argv){ 
printDevice(); 
printf("\n"); 
int h_array[ARRAY_SIZE]; 
const int ARRAY_BYTES = ARRAY_SIZE * sizeof(int);
int *d_array;  // 分配内存 
cudaMalloc((void **) &d_array, ARRAY_BYTES); 
cudaMemset((void *) d_array, 0, ARRAY_BYTES); 
increment_naive<<<NUM_THREADS/BLOCK_WIDTH, BLOCK_WIDTH>>>(d_array); 
cudaMemcpy(h_array, d_array, ARRAY_BYTES, cudaMemcpyDeviceToHost);
for(int i=0; i<ARRAY_SIZE; i++){ 
printf("%d:%d\n",i,h_array[i]); 
}  // 释放内存 
cudaFree(d_array); 
getchar();  //CUDA_SAFE_CALL(cudaGetDeviceCount(&deviceCount)); 
return 0;  } 

运行结果:(每次运行的结果是不确定的)

这里就需要引入原子操作,只需要将读写函数进行如下修改

__global__ void increment_atomicNaive(int *g) { 

       int i = blockIdx.x * blockDim.x + threadIdx.x; 
i = i % ARRAY_SIZE; 
atomicAdd(&g[i], 1);

运行结果:

使用原子操作也是有一定限制的,如下:

  • 只能使用一些特定的运算(如加、减、最小值、异或等运算,但是取模,求幂等运算则不行)和数据类型(一般是整型int)
  • 每个线程块里的不同线程以及线程块本身将以不定的顺序运行,我们在内存上用原子进行的运算顺序也是不定的。

    例如下面的计算表达式的记过会不一样:

    \((a+b+c)\) 和 \(a+(b+c)\),其中\(a=1,b=10^{99},c=-10^{99})\)
  • 虽然顺序不确定,但是要知道的是GPU还是会强制每个线程轮流访问内存,这把不同线程对内存的访问串行化

提高CUDA编程效率策略

  • 高运算密度(high arithmetic intensity)

    \(\frac{math}{memory}\)

前面提到了很多优化策略是集中在memory上的,把数据尽可能放到更快地内存上去,其中内存速度是

local > share > global

  • 避免线程发散(avoid thread divergence)

如图是线程发散的主要场景,即if else语句,上图右边非常生动的展现了线程发散的情形,可以看到各个线程在碰到if条件句后开始发散,最后聚合,但是最后各个线程之间的编号还是保持原来的不变的,这就是线程发散

下面举一个更加极端的例子,就是循环语句,如下图示:

可以看到有蓝、红、绿、紫四个线程同时运行,蓝线程只循环了一次,其他线程循环次数都多于蓝线程,当蓝线程退出循环后就不得不一直等着其他线程,上图左下角的示意图可以很直观的看到这大大降低了运行效率,这也是为什么我们需要避免线程发散

Summary

MARSGGBO♥原创







2017-11-1

【Udacity并行计算课程笔记】- Lesson 2 The GPU Hardware and Parallel Communication Patterns的更多相关文章

  1. Udacity并行计算课程笔记-The GPU Hardware and Parallel Communication Patterns

    本小节笔记大纲: 1.Communication patterns gather,scatter,stencil,transpose 2.GPU hardware & Programming ...

  2. 【Udacity并行计算课程笔记】- Lesson 4 Fundamental GPU Algorithms (Applications of Sort and Scan)

    I. Scan应用--Compact 在介绍这节之前,首先给定一个情景方便理解,就是因为某种原因我们需要从扑克牌中选出方块的牌. 更formal一点的说法如下,输入是 \(s_0,s_1,...\), ...

  3. 【Udacity并行计算课程笔记】- Lesson 3 Fundamental GPU Algorithms (Reduce, Scan, Histogram)

    本周主要内容如下: 如何分析GPU算法的速度和效率 ​​3个新的基本算法:归约.扫描和直方图(Reduce.Scan.Histogram) 一.评估标准 首先介绍用于评估GPU计算的两个标准: ste ...

  4. 【Udacity并行计算课程笔记】- lesson 1 The GPU Programming Model

    一.传统的提高计算速度的方法 faster clocks (设置更快的时钟) more work over per clock cycle(每个时钟周期做更多的工作) more processors( ...

  5. Udacity并行计算课程笔记-The GPU Programming Model

    一.传统的提高计算速度的方法 faster clocks (设置更快的时钟) more work over per clock cycle(每个时钟周期做更多的工作) more processors( ...

  6. udacity android 学习笔记: lesson 4 part b

    udacity android 学习笔记: lesson 4 part b 作者:干货店打杂的 /titer1 /Archimedes 出处:https://code.csdn.net/titer1 ...

  7. udacity android 实践笔记: lesson 4 part b

    udacity android 实践笔记: lesson 4 part b 作者:干货店打杂的 /titer1 /Archimedes 出处:https://code.csdn.net/titer1 ...

  8. udacity android 学习笔记: lesson 4 part a

    udacity android 学习笔记: lesson 4 part a 作者:干货店打杂的 /titer1 /Archimedes 出处:https://code.csdn.net/titer1 ...

  9. udacity android 实践笔记: lesson 4 part a

    udacity android 实践笔记: lesson 4 part a 作者:干货店打杂的 /titer1 /Archimedes 出处:https://code.csdn.net/titer1 ...

随机推荐

  1. BZOJ4828 AHOI/HNOI2017大佬(动态规划+bfs)

    注意到怼大佬的操作至多只能进行两次.我们逐步简化问题. 首先令f[i][j]表示第i天结束后自信值为j时至多有多少天可以进行非防御操作(即恢复自信值之外的操作).这个dp非常显然.由于最终只需要保证存 ...

  2. hdu 4897 Little Devil I (树链剖分+线段树)

    题目链接:  http://acm.hdu.edu.cn/showproblem.php?pid=4897 题意: 给你一棵树,一开始每条边都是白色,有三种操作: 1.将 u - v路径上的边转换颜色 ...

  3. MessageBox函数第一个参数hwnd的作用

    MessageBox 函数用于创建.显示并操作一个消息对话框.该对话框包含由调用程序定义的信息和标题,以及预先定义的图标和按钮. 这个方法的第一个参数hWnd,代表消息框拥有的窗口.这个参数到底有什么 ...

  4. BZOJ5312 冒险(势能线段树)

    BZOJ题目传送门 表示蒟蒻并不能一眼看出来这是个势能线段树. 不过仔细想想也并非难以理解,感性理解一下,在一个区间里又与又或,那么本来不相同的位也会渐渐相同,线段树每个叶子节点最多修改\(\log ...

  5. Python解惑:整数比较

    在 Python 中一切都是对象,毫无例外整数也是对象,对象之间比较是否相等可以用==,也可以用is.==和is操作的区别是: is比较的是两个对象的id值是否相等,也就是比较俩对象是否为同一个实例对 ...

  6. 自学Aruba6.2-控制器基本维护操作(web页面配置)

    点击返回:自学Aruba之路 自学Aruba6.2-控制器基本维护操作(web页面配置) 1 显示当前控制器版本 Dashboard---Controller中 2 升级Aruab os版本 Main ...

  7. luogu2774 [网络流24题]方格取数问题 (最小割)

    常见套路:棋盘黑白染色,就变成了一张二分图 然后如果选了黑点,四周的白点就不能选了,也是最小割的套路.先把所有价值加起来,再减掉一个最少的不能选的价值,也就是割掉表示不选 建边(S,黑点i,v[i]) ...

  8. [虚树模板] 洛谷P2495 消耗战

    题意:给定树上k个点,求切断这些点到根路径的最小代价.∑k <= 5e5 解:虚树. 构建虚树大概是这样的:设加入点与栈顶的lca为y,比较y和栈中第二个元素的DFS序大小关系. 代码如下: i ...

  9. 【LOJ#6278】数列分块2

    题目大意:分块维护一个有 n 个数字的序列,有两种操作:区间加,区间查询小于某个数的元素个数.n <= 50000 预处理阶段:处理出块内元素的相对大小顺序(排序),时间复杂度为 \(O(nlo ...

  10. c#中用lua脚本执行redis命令

    直接贴出代码,实现执行lua脚本的方法,用到的第三方类库是 StackExchange.Redis(nuget上有) 注:下面的代码是简化后的,实际使用要修改, using System; using ...