CUDA: GPU高性能运算
CUDA: GPU高性能运算
目录(?)[+]
0 序言
1 给自己提几个问题
问题1:GPU高性能运算之CUDA(下午简称CUDA技术)是干嘛用的,我为什么要学它?
好了,明白自己的需求和基础,给自己一个学习的定位,什么地方该花时间去琢磨,相比应该很清楚。
2 你好,GPU!
英伟达支持CUDA编程的显卡型号从G8800开始,都是可以的。一开始作为图像处理用,而今,天文地理、数学金融、医疗军事等等,都开始尝试发挥GPU的优势。
http://www.cnblogs.com/viviman/archive/2012/11/26/2789113.html
对于CPU和GPU分配自己的业务,稍微画个图失意一下,如图1:
总的来说,GPU只是干计算并行度高的功能模块的活,一定不可以越权啦!
3 你好,CUDA!
3.1 开发环境配置
http://www.cnblogs.com/viviman/archive/2012/11/05/2775100.html
在win7+vs2008+CUDA5.0的环境下体验,是一种新的尝试,我自己配的时候,很少或者就没有5.0的配置博客文档等等。因为5.0的那一场雪比2010年来的更晚一些……
3.2 特殊的"hello world"
- __global__ void hello(char *ch)
- {
- ch = {'h', 'e', 'l', 'l', 'o'};
- }
- int main()
- {
- ……
- hello<<<1,1>>>(dev_ch);
- ……
- return 0;
- }
I think you know my idea.
在你网上搜索到的程序的基础上,作这样的一个改变,相信自己动手的才是快乐的。
4 敲开编程的门
我习惯性的喜欢先看一门语言的关键字,CUDA的关键字很简单很少:
函数类型
比如__global__ void kerneladd(float *a){}
__host__: 主机函数,供主机函数调用,可缺省哦,一般情况下,都是缺省的,知道这个东西就行。
存储类型
__shared__: 共享存储,每个block内的线程共享这个存储。
__constant__:常量存储,只读。定义在所有函数之外,作用范围整个文件。
内建变量
dim3, threadId, blockId, gridId
5 GPU也不允许偏心
并行的事情多了,我们作为GPU的指令分配者,不能偏心了——给甲做的事情多,而乙没事做,个么甲肯定不爽的来。所以,在GPU中,叫做线程网络的分配。首先还是来看下GPU的线程网络吧,图2:
我们将具体点的,在主机函数中如果我们分配的是这样的一个东西:
dim3是神马?dim3是一个内置的结构体,和linux下定义的线程结构体是个类似的意义的东西,dim3结构变量有x,y,z,表示3维的维度。不理解没关系,慢慢看。
kernelfun<<<blocks, threads>>>();
那我们的内核函数kernelfun()如何知道自己执行的是哪个线程?这就是线程网络的特点啦,为什么叫网络,是有讲究的,网络就可以定格到网点:
比如int tid = threadId.x + blockId.x * 16
对于(Dx,Dy)二维的block,tid = threadId.x + Dx*threadId.y
对于(Dx,Dy,Dz)三维的block,tid = threadId.x + Dx*threadId.y + Dz*Dy*threadId.z
kerneladd<<<dimGrid, dimBlock>>>();
这可是万金油啊,你需要做的事情是填充dimGrid和dimBlock的结构体构造函数变量,比如,dimGrid(16, 16)表示用了16*16的二维的block线程块。
(,)是(dimGrid.x, dimGrid.y)的网格编号。
我们这么理解吧,现在又一群人,我们分成16*16个小组(block),排列好,比如第3行第4列就指的是(2,3)这个小组。
唠叨了这么多,下面我们用一个最能说明问题的例子来进一步理解线程网络分配机制来了解线程网络的使用。
一维网络线程
eg:int arr[1000],对每个数组元素进行加1操作。
idea:我们最直接的想法,是调度1000个线程去干这件事情。
好吧,cuda定义了一个结构体cudaDeviceProp,里面存入了一系列的结构体变量作为GPU的参数,出了maxThreadsPerBlock,还有很多信息哦,我们用到了再说。
好吧,我们的数组元素1000个,是可以在一个block中干完的。
- #define N 1000
- __gloabl__ void kerneladd(int *dev_arr)
- {
- int tid = threadId.x;
- if (tid < 1000)
- dev_arr[tid] ++;
- }
- int main()
- {
- int *arr, *dev_arr;// 习惯的我喜欢在内核函数参数变量前加个dev_作为标示
- // 开辟主机内存,arr = (int*)malloc(N*sizeof(int));
- // 开辟设备内存
- // 主机拷贝到设备
- kerneladd<<<1, N>>>(dev_arr);
- // 设备拷贝到主机
- // 打印
- // 释放设备内存
- // 释放主机内存
- return 0;
- }
呀,原来这么简单,个么CUDA也忒简单了哇!这中想法是好的,给自己提高信心,但是这种想法多了是不好的,因为后面的问题多了去了。
盆友说,1000个元素,还不如CPU来的快,对的,很多情况下,数据量并行度不是特别大的情况下,可能CPU来的更快一些,比较设备与主机之间互相调度操作,是会有额外开销的。有人就问了,一个10000个元素的数组是不是上面提供的idea就解决不了啦?对,一个block人都没怎么多,如何完成!这个情况下有两条路可以选择——
第一,我就用一个组的1000人来干活话,每个人让他干10个元素好了。
这个解决方案,我们需要修改的是内核函数:
- __global__ void kernelarr(int *dev_arr)
- {
- int tid = threadId.x;
- if(tid < 1000) // 只用0~999号线程
- {
- //每个线程处理10个元素,比如0号线程处理0、1001、2001、……9001
- for(int i = tid; i<N; i=i+1000)
- {
- dev_arr[tid] ++;
- }
- }
- }
第二,我多用几个组来干这件事情,比如我用10个组,每个组用1000人。
这个解决方案就稍微复杂了一点,注意只是一点点哦~因为,组内部怎么干活和最原始的做法是一样的,不同之处是,我们调遣了10个组去干这件事情。
首先我们来修改我们的主机函数:
- int main()
- {
- ……
- kerneladd<<<10, 1000>>>(dev_arr);//我们调遣了10个组,每个组用了1000人
- ……
- }
盆友要问了,10个组每个组1000人,你怎么点兵呢?很简单啊,第1组第3个线程出列,第9组第9个线程出列。每个人用组号和组内的编号定了位置。在线程网络中,blockId.x和threadId.x就是对应的组号和组内编号啦,我必须要这里开始形象点表示这个对应关系,如果这个对应关系是这样子的[blockId.x,threadId.x],那么我们的数组arr[10000]可以这样分配给这10个组去干活:
(0,0)——arr[0],(0,1)——arr[1],……(0,999)——arr[999]
(1,0)——arr[0+1*1000],(1,1)——arr[1+1*1000],……(1,999)——arr[999+1*1000]
……
(9,0)——arr[0+9*1000],(9,1)——arr[1+9*1000],……(9,999)——arr[999+9*1000]
是不是很有规律呢?对的,用blockId.x和threadId.x可以很好的知道哪个线程干哪个元素,这个元素的下表就是threadId.x + 1000*blockId.x。
这里我想说的是,如果我们哪天糊涂了,画一画这个对应关系的表,也许,就更加清楚的知道我们分配的线程对应的处理那些东西啦。
一维线程网络,就先学这么多了。
二维网络线程
第一个念头,开个32*16个线程好了哇,万事大吉!好吧。但是,朕现在想用二维线程网络来解决,因为朕觉得一个二维的网络去映射一个二维的数组,朕看的更加明了,看不清楚自己的士兵,如何带兵打仗!
一个block中,现在是一个二维的thread网络,如果我用了16*16个线程。
一个grid中,block也可以是二维的,一个block中已经用了16*16的thread了,那我们一共就32*16个元素,我们用2个block就行了。
先给出一个代码清单吧,程序员都喜欢看代码,这段代码是我抄袭的。第一次这么完整的放上代码,因为我觉得这个代码可以让我说明我想说的几个问题:
- #include <stdio.h>
- #include <stdlib.h>
- #include <cuda_runtime.h>
- #define ROWS 32
- #define COLS 16
- #define CHECK(res) if(res!=cudaSuccess){exit(-1);}
- __global__ void Kerneltest(int **da, unsigned int rows, unsigned int cols)
- {
- unsigned int row = blockDim.y * blockIdx.y + threadIdx.y;
- unsigned int col = blockDim.x * blockIdx.x + threadIdx.x;
- if (row < rows && col < cols)
- {
- da[row][col] = row * cols + col;
- }
- }
- int main(int argc, char **argv)
- {
- int **da = NULL;
- int **ha = NULL;
- int *dc = NULL;
- int *hc = NULL;
- cudaError_t res;
- int r, c;
- bool is_right = true;
- res = cudaMalloc((void**)(&da), ROWS * sizeof(int*)); CHECK(res)
- res = cudaMalloc((void**)(&dc), ROWS * COLS * sizeof(int)); CHECK(res)
- ha = (int**)malloc(ROWS * sizeof(int*));
- hc = (int*)malloc(ROWS * COLS * sizeof(int));
- for (r = 0; r < ROWS; r++)
- {
- ha[r] = dc + r * COLS;
- }
- res = cudaMemcpy((void*)(da), (void*)(ha), ROWS * sizeof(int*), cudaMemcpyHostToDevice); CHECK(res)
- dim3 dimBlock(16, 16);
- dim3 dimGrid((COLS + dimBlock.x - 1) / (dimBlock.x), (ROWS + dimBlock.y - 1) / (dimBlock.y));
- Kerneltest<<<dimGrid, dimBlock>>>(da, ROWS, COLS);
- res = cudaMemcpy((void*)(hc), (void*)(dc), ROWS * COLS * sizeof(int), cudaMemcpyDeviceToHost); CHECK(res)
- for (r = 0; r < ROWS; r++)
- {
- for (c = 0; c < COLS; c++)
- {
- printf("%4d ", hc[r * COLS + c]);
- if (hc[r * COLS + c] != (r * COLS + c))
- {
- is_right = false;
- }
- }
- printf("\n");
- }
- printf("the result is %s!\n", is_right ? "right" : "false");
- cudaFree((void*)da);
- cudaFree((void*)dc);
- free(ha);
- free(hc);
- getchar();
- return 0;
- }
简要的来学习一下二维网络这个知识点,
dim3 dimBlock(16, 16);
//定义block内的thread二维网络为16*16
dim3 dimGrid((COLS + dimBlock.x - 1) / (dimBlock.x), (ROWS + dimBlock.y - 1) / (dimBlock.y));
//定义grid内的block二维网络为1*2
unsigned int row = blockDim.y * blockIdx.y + threadIdx.y;
//二维数组中的行号
unsigned int col = blockDim.x * blockIdx.x + threadIdx.x;
//二维线程中的列号
三维网络线程
dim3定义了三维的结构,但是,貌似二维之内就能处理很多事情啦,所以,我放弃学习三维。网上看到的不支持三维网络是什么意思呢?先放一放。
给自己充充电
同一块显卡,不管你是二维和三维或一维,其计算能力是固定的。比如一个block能处理1024个线程,那么,一维和二维线程网络是不是处理的线程数一样呢?
回答此问题,先给出网络配置的参数形式——<<<Dg,Db,Ns,S>>>,各个参数含义如下:
Ns:这个是可选参数,设定最多能动态分配的共享内存大小,比如16k,单不需要是,这个值可以省略或写0。
S:也是可选参数,表示流号,默认为0。流这个概念我们这里不说。
接着,我想解决几个你肯定想问的两个问题,因为我看很多人想我这样的问这个问题:
答:不要,一般来说,我们开128或256个线程,二维的话就是16*16。
答:牛人告诉我,一般来说是你的流处理器的4倍以上,这样效率最高。
6 规约思想和同步概念
扩大点说,并行计算是有一种基本思想的,这个算法能解决很多很常规的问题,而且很实用,比如说累加和累积等——规约思想。对于基础的、重要的,我想有必要系统的学习。
http://www.cnblogs.com/viviman/archive/2012/11/21/2780286.html
cpu计算:显然单核的话需要64*256*t。我们容忍不了。
- __global__ void RowSum(float* A, float* B)
- {
- int bid = blockIdx.x; int tid = threadIdx.x;
- __shared__ s_data[128]; //read data to shared memory
- s_data[tid] = A[bid*128 + tid];
- __synctheads(); //sync
- for(int i=64; i>0; i/=2)
- {
- if(tid<i) s_data[tid] = s_data[tid] + s_data[tid+i] ;
- __synctheads();
- }
- if(tid==0)
- B[bid] = s_data[0];
- }
这个例子还让我学到另一个东西——同步!我先不说同步是什么,你听我说个故事:我们调遣了10个小组从南京去日本打仗,我们的约定是,10个组可以自己行动,所有组在第三天在上海机场会合,然后一起去日本。这件事情肯定是需要处理的,不能第1组到了上海就先去日本了,这些先到的组,唯一可以做的事情是——等待!这个先来后到的事情,需要统一管理的时候,必须同步一下,在上海这个地方,大家统一下步调,快的组等等慢的组,然后一起干接下去的旅程。
是不是很好理解,这就是同步在生活中的例子,应该这样说,计算机的所有机制和算法很多都是源于生活!结合起来,理解起来会简单一点。
在CUDA中,我们的同步机制用处大吗?又是如何用的呢?我告诉你,一个正常规模的工程中,一般来说数据都会有先来后到的关系,这一个计算结果可能是提供给另一个线程用的,这种依赖关系存在,会造成同步的应用。
__synctheads()这句话的作用是,这个block中的所有线程都执行到此的时候,都听下来,等所有都执行到这个地方的时候,再往下执行。
7 撬开编程的锁
锁是数据相关性里面肯定要用到的东西,很好,生活中也一样,没锁,家里不安全;GPU中没锁,数据会被“盗”。
对于存在竞争的数据,CUDA提供了原子操作函数——ATOM操作。
- __global__ void kernelfun()
- {
- __shared__ int i=0;
- atomicAdd(&i, 1);
- }
如果没有加互斥机制,则同一个half warp内的线程将对i的操作混淆林乱。
用原子操作函数,可以很简单的编写自己的锁,SKD中有给出的锁结构体如下:
- #ifndef __LOCK_H__
- #define __LOCK_H__
- #include "cuda_runtime.h"
- #include "device_launch_parameters.h"
- #include "atomic_functions.h"
- struct Lock {
- int *mutex;
- Lock( void ) {
- HANDLE_ERROR( cudaMalloc( (void**)&mutex, sizeof(int) ) );
- HANDLE_ERROR( cudaMemset( mutex, 0, sizeof(int) ) );
- }
- ~Lock( void ) {
- cudaFree( mutex );
- }
- __device__ void lock( void ) {
- while( atomicCAS( mutex, 0, 1 ) != 0 );
- }
- __device__ void unlock( void ) {
- atomicExch( mutex, 0 );
- }
- };
- #endif
8 CUDA软件体系结构
9 利用好现有的资源
这里我先不详细学习这些库里到底有哪些函数,但是,大方向是需要了解的,不然找都不知道去哪儿找。CUFFT是傅里叶变换的库,CUBLAS提供了基本的矩阵和向量运算,CUDPP提供了常用的并行排序、搜索等。
CUDA4.0以上,提供了一个类似STL的模板库,初步窥探,只是一个类似vector的模板类型。有map吗?map其实是一个散列表,可以用hashtable去实现这项机制。
http://blog.csdn.net/huangfengxiao/article/details/8732789
http://blog.csdn.net/huangfengxiao/article/details/8732790
http://blog.csdn.net/huangfengxiao/article/details/8732791
CUDA: GPU高性能运算相关推荐
- 《GPU高性能编程CUDA实战》中代码整理
CUDA架构专门为GPU计算设计了一种全新的模块,目的是减轻早期GPU计算中存在的一些限制,而正是这些限制使得之前的GPU在通用计算中没有得到广泛的应用. 使用CUDA C来编写代码的前提条件包括:( ...
- GPU高性能编程CUDA实战(二)
视觉IMAX的第45篇文章 前言 在上一篇文章中: CUDA工程的建立(两种方法) 第一种方法: 这种方法在 接下来实施「三步走战略」配置「附加包含目录」.「附加库目录」以及「附加依赖项」.第一步:配 ...
- CPU与CUDA(GPU)的计算能力对比之二: Keras Resnet 运算效率比较
CPU与CUDA(GPU)的计算能力对比之二: Keras Resnet 运算效率比较 结论: CUDA(GPU : NVIDIA RTX2070 MQ 笔记本版本) 启动后,效率将近是 CPU 单独 ...
- 超算和服务器性能,烽火GPU高性能服务器助力武大超算中心建设
近日,继"武汉大学100G高速网络及网格计算集群采购项目"中标后,烽火GPU高性能服务器再次中标"武汉大学GPU集群采购项目".作为行业首例,此次烽火将在武大超 ...
- matlab 中使用 GPU 加速运算
为了提高大规模数据处理的能力,matlab 的 GPU 并行计算,本质上是在 cuda 的基础上开发的 wrapper,也就是说 matlab 目前只支持 NVIDIA 的显卡. 1. GPU 硬件支 ...
- 《GPU高性能编程》——gl_helper.h
<GPU高性能编程 CUDA实战> 书中 gl_helper.h 文件分享 <GPU高性能编程CUDA实战>是一本不错的好书,其中所含示例代码,经常包含有 "gl_h ...
- CPU与CUDA(GPU)的计算能力对比之一: Tensorflow矩阵乘
CPU与CUDA(GPU)的计算能力对比之一: Tensorflow矩阵乘 结论: 1.Tensorflow 矩阵乘场景,CUDA 的效率是 CPU 的 1000 倍以上. 2. 测试过程中: GPU ...
- 适用于CUDA GPU的Numba例子
• 适用于CUDA GPU的Numba例子 矩阵乘法 这是使用CUDA内核的矩阵乘法的简单实现: @cuda.jit def matmul(A, B, C): """Pe ...
- python调用gpu进行运算_tensorflow指定CPU与GPU运算的方法实现
1.指定GPU运算 如果安装的是GPU版本,在运行的过程中TensorFlow能够自动检测.如果检测到GPU,TensorFlow会尽可能的利用找到的第一个GPU来执行操作. 如果机器上有超过一个可用 ...
最新文章
- linux双网卡绑定实现冗余与负载均衡
- 购物价值观(values of shopping)
- 使用Oauth2实现微服务的安全保护
- 在前后端分离的路上承受了多少痛?看看这篇是否能帮到你?
- 小程序云函数获取用户昵称_小程序云开发云函数进阶
- small用于不连续数组_Excel公式技巧19: 在方形区域内填充不重复的随机整数
- 【Boost】boost库中function和bind一起使用的技巧(一)
- 当前时间转换的秒数_C++ 日期 amp; 时间
- 软件测试框架课程考试_那考试准备课程值得吗?
- Redis面试 - Redis 主从架构
- 接口不能被实例化的吗?接口引用是什么?
- 比波士顿动力快一步:两足机器人送快递,你不用跑出门也能收货了
- secoclient在Mac下使用无法上网的解决办法
- vue 实现图片预览
- vmware虚拟机使用教程
- 个人企业信息多用户入驻智能电子名片小程序开发
- 路由器重温——MP配置管理
- css3复习知识点概括1(根据W3S顺序)
- 干货!史上最全数据分析学习路线(附资源链接)
- Objective-C 【对象-多文件开发简介】