GPU和CUDA基础
1.显卡、GPU、CUDA
显卡的作用
- 将电脑的数字信号转换成模拟信号让显示器显示出来
独立显卡和集成显卡的区别
- 所谓集成,是指显卡集成在主板上,不能随意更换。
- 而独立显卡是作为一个独立的器件插在主板的接口上的,可以随时更换升级。
- 集成显卡使用物理内存,而独立显卡有自己的显存。
NVIDIA显卡分类
- GeForce系列:家庭娱乐
- Quadro系列:专业绘图设计
- Tesla系列:高端显卡,大规模并行运算
显卡和GPU的关系
- GPU是显卡上的一块芯片。
- 集成显卡和独立显卡都是有GPU的。
GPU的发展历程
- 仅用于图形渲染,此功能是GPU的初衷
- 后来人们发现,GPU这么一个强大的器件只用于图形处理太浪费了,它应该用来做更多的工作,例如浮点运算。怎么做呢?直接把浮点运算交给GPU是做不到的,因为它只能用于图形处理(那个时候)。最容易想到的,是把浮点运算做一些处理,包装成图形渲染任务,然后交给GPU来做。这就是 GPGPU(General Purpose GPU)的概念。不过这样做有一个缺点,就是你必须有一定的图形学知识,否则你不知道如何包装。
- 于是,为了让不懂图形学知识的人也能体验到GPU运算的强大,Nvidia公司又提出了CUDA的概念。
CUDA
- CUDA(Compute Unified Device Architecture),通用并行计算架构,是一种运算平台。它包含CUDA指令集架构以及GPU内部的并行计算引擎。你只要使用一种类似于C语言的 CUDA C语言,就可以开发CUDA程序,从而可以更加方便的利用GPU强大的计算能力,而不是像以前那样先将计算任务包装成图形渲染任务,再交由GPU处理。
- 注意,并不是所有GPU都支持CUDA。
CPU和GPU
- GPU是吞吐导向内核,CPU是延迟导向的内核
- CPU擅长逻辑性强的运算,串行计算
- GPU擅长高度线程化的并行处理任务
英伟达GPU架构
- Tesla (特斯拉)
- Fermi(费米)
- Kepler(开普勒)
- Maxwell(麦克斯韦)
- Pascal(帕斯卡)
- Volta(伏特)
- Turing(图灵)
- Ampere(安培)
- Hopper(赫柏)
显存和内存
- 显存是帮GPU存储的,内存是帮CPU存储的
GPU能与不能
- GPU缓存小,不适合处理频繁访问内存的操作
- GPU控制单元简单,不适合处理控制复杂的指令
- GPU有大量的计算单元,适合计算密集型程序
- GPU每一行的运算单元控制指令是一个,适合并行度高的程序
2.CUDA编程基础
CUDA并行计算流程
- 申请显存 cudaMalloc
- 转移拷贝 cudaMemcpy
- Host --> Device
- Device --> Host
- 释放显存 cudaFree
CUDA硬件描述
- Device: GPU
- Host: CPU
- Kernel: GPU上运行的函数
CUDA内存模型
硬件名称 | 软件名称 |
---|---|
线程处理器(SP) | 线程(thread) |
多核处理器(SM) | 线程块(thread block) |
设备端(device) | 线程块组合体(grid) |
- 一个GPU有多个SM,这些SM有一个全局内存,所有的线程都可以访问
- 每个SM都有一个共享内存,仅可以被线程块内的所有线程访问
- 一个SM有多个SP,每个SP有一个局部内存,仅可以被该线程访问
- 一个kernel由一个gird来执行,也就是一个kernel一次只能在一个GPU上执行
- 一个线程块内的线程可以协作,不同线程块内的线程不能协作,线程块之间相互独立
- 每个线程都有一个索引,用于计算内存地址
线程束
- SM采用SIMT(Single-Instruction, Multiple-Thread, 单指令多线程)架构,线程束(warp)是最基本的执行单元
- 线程束是指一个包含32个线程的集合,线程束中的每一个线程都将在不同的数据上执行相同的指令;(一个控制单元控制一行的计算单元,这些计算单元以不同的数据资源执行相同的指令)
- 线程束本质上是线程在GPU上运行的最小单元
- 线程块所包含的线程大小一般要设置为32的倍数
cudaEvent
// 记录时间
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start, 0);
// ...
// 记录结束时刻
cudaEventRecord(stop, 0);
// 确保GPU执行完了前面的所有工作再读取stop时间
// cudaEvent是在GPU上执行的,因此不适合对同时包含设备代码和主机代码的混合计时
cudaEventSynchronize(stop);
// // 计算时刻差
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
printf("Time of GPU: %3.1f ms\n", elapsedTime);
cudaEventDestroy(start);
cudaEventDestroy(stop);
- cudaEventElapsedTime: 计算两个时间之间经历的时间
- cudaEventDestroy: 使用完事件后要销毁事件(当做规定)