CUDA笔记(一)

Access the Power of GPU

  • 一般有三种方法来实现加速:Libraries or OpenACC or Programming Languages
  • 函数库:

例如:

cuBLAS: 线性代数库

cuSPARSE: 稀疏矩阵的线性代数库

cuDNN: 深度神经网络

GPU Architecture

异构计算(Heterogeneous Computing)

Terminology:

  • Host: The CPU and tis memory(host memory) 主机端(CPU)
  • Device: The GPU and its memory(device memory) 设备端(GPU)

CPU与GPU的简易架构图

  • CPU有很多逻辑控制单元,而GPU有很多处理核心
  • CPU是缓存优化的处理器,缓存特别大,而且有很多控制单元
  • GPU是一个并行吞吐优化的处理器,计算核心特别多
  • 因此我们知道,GPU的单精度处理能力比CPU快很多的原因其实就是GPU将更多的晶体管用于了计算而不是缓存

程序的开发中如何选择CPU和GPU

  • 从图中可以看到CPU的缓存延迟非常短,这也对应它的架构里有很多复杂的逻辑控制
  • GPU则不同,图中的W是warp,1 warp = 32 Threads,GPU是以warp为单位进行切换的。我们可以通过海量的线程的切换来对缓存延迟做隐藏,如果你的程序是计算密集型的,而且有很好的并行特性,那么gpu是更好的选择。
  • GPU的架构有两个部分组成,第一部分是计算单元,第二部分是内存单元
  • 图中的绿色部分就是一个CUDA Core
  • 从图中可以看到,GPU和CPU是通过PCle进行链接的
  • 一个GPU有很多SM(流多处理器)组成,每一个SM有很多核,因此有越多的SM,那么GPU就能在同一时间内处理更多的任务。

SM的架构

  • Kepler架构中的SM称为SMX,表示处理器更强劲,其逻辑控制单元是一个整体,控制192个CUDA Cores
  • Maxwell架构中的SM称为SMM,其逻辑控制单元有4个,每个控制32个CUDA Cores
  • 可以简单认为,一个SMM中包含了4个SMX,这样的话就避免了因为逻辑控制单元过少而造成核心的冗余,效率更高。从架构上来看,Maxwell一个CUDA核心相比于Kepler提升了35%

Memory Hierarchy

因为经常要跟内存打交道,所以需要对GPU的内存架构非常熟悉,GPU的架构分为三个层次,如图所示,和CPU类似,可以看做成一个金字塔,从下往上越来越快,首先是Global Memory,有很大的内存空间,它的空间很大,但是缓存延迟很高,因此我们需要加入一个缓存,称之为L2缓存,GPU的缓存要像Global Memory一样读取数据的话,首先我们要看这些数据是否在L2中已经有缓存,有的话称之为缓存命中,这样就不用再从Global Memory里面读数据,这样就加快了读写。L2缓存是多个缓存所共享的。再上面有很多寄存器,L1缓存等。对于不同的内存有不同的使用和优化方案。

GPU in Computer System

CUDA Programming Basics

配置CUDA开发环境

  • 注意如果使用C/C++进行开发的话,我们使用的是NVCC的编译器

Heterogeneous Computing 异构编程

  • 一般我们把GPU运行的程序构造成一个函数,在我们需要的时候进行调用,这个函数就是在GPU上运行

  • 这个函数的之前和之后都是在CPU上单线程运行的,因此我们把整个程序分为:顺序执行,并行执行,顺序执行


  • 顺序执行就是利用CPU的一个线程来执行

  • 并行执行就是用GPU的多线程来进行计算

  • 因此如果一部分功能是计算密集型的,我们就可以把它写成一个函数,对它进行并行,并不是把所有的代码都在GPU上运行,而是把需要的部分放到GPU上

CUDA Kernels

这里我们讲解一个概念,我们要将一部分代码在gpu上运行,这部分代码是一个函数,我们称为一个kernel(核函数),CPU(Host)执行functions,GPU(Device)执行kernels。

Hello World

//hello_world.c
#include <stdio.h>

void hello_world_kernel()
{
    printf("Hello World\n");
}

int main()
{
    hello_world_kernel();
}

Compile & Run:
gcc hello_world.c
./a.out

在GPU上执行:

//hello_world.cu
#include <stdio.h>

_global_ hello_world_kernel()
{
    printf("Hello World\n");
}

int main()
{
    hello_world_kernel<<<1.1>>>();
}

Compile & Run:
nvcc hello_world.cu
./a.out

不同之处

  • 文件后缀名改为.cu
  • global表示了该函数为一个核函数,标识这个函数会在gpu上运行
  • “<<<...,...>>>”具体含义是GPU中的一个配置
  • 编译器改为nvcc

GPU memory management

hello world程序非常简单明了,但是在编程过程中我们经常要和内存打交道,那么如何进行GPU内存管理呢?

我们很熟悉对CPU的内存管理,我们会使用

malloc();//动态分配内存
memset();//对空间进行初始化
free();//释放一段内存

对应于,在GPU中也有类似的函数,它们分别是

cudaMalloc(void** pointer, sie_t nbytes);//pointer指定空间,大小是nbytes
cudaMemset(void* pointer, int value, size_t count);
cudaFree(void* pointer);

例如:

int nbytes = 1024*sizeof(int);
int* d_a = 0;
cudaMalloc((void**)&d_a, nbytes);
cudaMemset(d_a, 0, nbytes);
cudaFree(d_a);

因此一定要分清楚,哪部分数据在cpu上,哪部分数据在gpu上

Data Copies

__host__ cudaMemcpy(void* dst, void* src, size_t nbytes, cudaMemcpyKind direction);
  • global是在gpu上进行的,host就是在cpu上执行
  • 四个形参:拷贝目的指针,拷贝原指针,拷贝大小,拷贝方向
  • 线程阻塞的,拷贝不完成,cpu不会执行下面的代码
  • 常见拷贝方向有:cudaMemcpyHostToDevice(CPU到GPU)、cudaMemcpyDeviceToHost(GPU到CPU)、cudaMemcpyDeviceToDevice(GPU到GPU)
  • 设定拷贝方向时,需要注意src和dst的指针是CPU的还是GPU的,要和拷贝方向一致
  • 上述函数为线程阻塞的,同样还有一个异步的函数:cudaMemcpyAsync();

三步流程

第一步:通过调用cudaMemcpy函数将CPU中的数据拷贝到GPU中,通过PCI Bus,方向就是HostToDevice

第二步:通过CPU启动kernel核函数,然后开始一个并行计算

Kernel<<<grid, block>>>();

第三步:计算完以后,再调用cudaMemcpy将数据从GPU端拷贝到CPU端,方向是DeviceToHost

例子:

代码分为五个步骤:

  • 在CPU端分配n个整型变量的空间
  • 在GPU端分配n个整型变量的空间
  • 初始化GPU内存为0
  • 将数据从GPU拷贝到CPU
  • 打印变量
#include<stdio.h>

int main()
{
    int dimx = 16;
    int num_bytes = dimx * sizeof(int);
    int *d_a = 0, *h_a = 0;//设备端和主机端的指针
    
    //在CPU端分配n个整型变量的空间
    h_a = (int*)malloc(num_bytes);
    //在GPU端分配n个整型变量的空间
    cudaMalloc((void**) &d_a, num_bytes);
    
    if(0 == h_a || 0 == d_a)
    {
        printf("Couldn't allocate memory\n");
        return 1;
    }
    
    //初始化GPU内存为0
    cudaMemset(d_a,0,num_bytes);
    
    //将数据从GPU拷贝到CPU
    cudaMemcpy(h_a, d_a, num_bytes, cudaMemcpyDeviceToHost);
    
    //打印变量
    for(int i=0; i < dimx; i++)
    {
        printf("%d\t", h_a[i]);
    }
    free(h_a);//主机端释放
    cudaFree(d_a);//设备端释放
    return 0;
}
  • 这里注意,cudaMemcpy是CPU阻塞的,也就是说,不执行完该函数CPU就不会一直向下执行。如果换成一个异步的版本,CPU会在拷贝还没有完成的时候就开始打印,打印的时候很多是错误的。

上面的示例代码缺少了GPU上面的并行计算,那么我们下一面将开始讲解如何写Kernel函数。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容