GPU基础与CUDA编程入门

news2024/9/23 17:11:05

文章目录

  • 一、GPU和CPU的区别
    • GPU: 高吞吐量导向设计
    • CPU: 低延迟导向设计
    • GPU适合什么场景:
    • 什么是Prefetch?
  • 二、CUDA与OpenCL
  • 三、CUDA编程并行计算整体流程
    • 内存模型
    • 线程块
    • 网格Grid:并行线程块组合
    • 线程束
    • SIMD
  • 四、CUDA编程实例:向量相加
    • CUDA 编译流程![请添加图片描述](https://img-blog.csdnimg.cn/0e02eee2247c4f4b8b44bf301435fee7.png)
  • 五. 代码实例


一、GPU和CPU的区别

GPU: 高吞吐量导向设计

  1. 缓存少: 提高内存吞吐
  2. 控制简单: 没有分支预测机制和数据转发机制,但是同样存在Prefetch机制。
  3. 运算单元精简: 长延时流水线来实现高吞吐量,需要大量线程来容忍延迟。
  4. 适合场景: 并行计算占比多,吞吐优先,GPU单位时间执行指令数大大超过CPU

CPU: 低延迟导向设计

  1. 大内存,多分级缓存。多级缓存结构提高缓存速度。
  2. 控制复杂: 具备分支预测机制和流水线Prefetch机制,加速数据读取。
  3. 运算单元强大: CPU对复杂的整型和浮点型的运算速度支持较好,速度快。
  4. 适合场景: 连续计算部分,对时延要求高,对单条复杂指令延迟远远低于GPU

GPU适合什么场景:

计算密集,当数值计算的比例远远高于内存操作时; 数据并行,当一个大任务可以拆分成若干个小任务时,因此对复杂流程控制的需求较低

什么是Prefetch?

预取是一种内存管理策略,旨在减少内存访问延迟,从而提高计算性能。预取机制通过预先加载数据到高速缓存(例如,从全局内存到共享内存或纹理内存)来实现这一目标,以便在执行计算任务时减少等待时间。

GPU的预取机制有两种形式:

  1. 硬件预取:这是由GPU硬件自动实现的预取机制,不需要程序员进行显式操作。GPU内部的内存控制器会预测内存访问模式,提前将可能需要的数据加载到高速缓存中。这种预取机制在许多现代GPU架构(如NVIDIA的Pascal、Volta和Ampere架构)中都有实现。

  2. 软件预取:程序员可以通过编写代码显式地实现预取,以便更好地控制数据加载的过程。在CUDA编程中,可以使用__builtin_prefetch()函数来实现软件预取,该函数将根据程序员的指示将数据加载到L1或L2高速缓存中。软件预取的好处是程序员可以根据任务的特点精确地控制预取行为,从而进一步提高性能。

实际上,预取机制是一种平衡延迟和吞吐量的策略,旨在最大限度地提高GPU的计算效率。需要注意的是,预取机制在不同的GPU架构和设备上可能有所差异。因此,在优化GPU代码时,需要充分了解目标硬件的特性。

二、CUDA与OpenCL

CUDA(Compute Unified Device Architecture)和OpenCL(Open Computing Language)是用于加速计算的并行计算框架。

CUDA是由英伟达公司开发的框架,支持在NVIDIA的GPU上运行。CUDA提供了一组库和工具,可让开发人员使用C、C++和Fortran等编程语言来编写GPU加速的应用程序。CUDA的优点是它的性能非常高,而且支持广泛的NVIDIA GPU硬件,这使得它成为开发GPU加速应用程序的首选框架之一。

OpenCL是一个由多家公司共同开发的框架,可以在支持OpenCL的GPU、CPU和其他处理器上运行。OpenCL的优点是它是一个跨平台的框架,这意味着可以在不同的硬件和操作系统上运行。OpenCL还支持多种编程语言,包括C、C++、Java和Python等。

虽然CUDA和OpenCL都是用于加速计算的框架,但它们有一些不同之处。CUDA主要用于NVIDIA GPU上的计算,而OpenCL则可以在不同的硬件上运行。此外,CUDA的编程模型比较简单,而OpenCL则更加灵活。选择哪种框架取决于具体的应用场景和硬件设备。

三、CUDA编程并行计算整体流程

假设有这么一个GPU Kernel Function:

void GPUKernel(float *A,float *B,float *C,int n){}
其流程可以分为下面几个步骤:
1. Allocate GPU memory for A and B and C.
2. Copy A, B to GPU memory.
3. Run GPUKernel Function to have the GPU perform the actual vector operator.
4. Copy C from GPU to CPU.

内存模型

内存模型是CUDA编程中的核心。其内存模型可以分为如下几个层次:

  1. 每一个线程处理器SP都拥有自己的寄存器。
  2. 每一个线程处理器SP都有自己的Local Memory, 且Register和Local Memory只能被该线程进行访问。
  3. 每一个多核处理器(SM)内部都有自己的shared memory, shared memory 可以被线程块内部所有线程访问。
  4. 所有SM共有一块Global Shared Memory,可以被不同核的不同线程块的所有线程进行访问
    请添加图片描述

线程块

线程块是将线程数组分成多个块的结构。块内的线程通过共享内存,原子操作和屏障同步进行同步和协作。不同块中的线程不能进行协作。如下图,一个线程使用256个线程进行向量相加,最终将结果进行同步
请添加图片描述

网格Grid:并行线程块组合

每一个线程块中的每一个线程都有一个索引,用于计算内存地址和做出控制决策
请添加图片描述
我们使用线程块Block ID和线程Thread ID来定位每一个独立线程:
请添加图片描述
线程ID计算公式:

请添加图片描述

线程束

线程束(warp)是 GPU 中执行并行计算任务的基本单元,它由一组线程组成,可以同时执行相同的指令序列。在软件端,程序员将并行计算任务编写成 CUDA 或 OpenCL 的代码,并将代码编译成适合 GPU 的指令集。

在硬件端,GPU 的计算单元可以同时执行大量的线程束,每个线程束中包含了一定数量的线程。当计算单元收到一个指令序列时,它会同时启动多个线程束来执行这个指令序列。每个线程束中的线程都会同时执行相同的指令,但是对于每个线程的输入数据和输出结果是不同的。

为了高效地执行线程束,GPU 通常采用SIMD(Single Instruction Multiple Data)架构。这种架构允许计算单元同时执行多个相同的指令,但是每个指令所操作的数据可以不同。因此,在执行一个线程束时,GPU 可以高效地利用 SIMD 架构,同时处理多个线程的计算任务。

在具体实现上,GPU 的控制单元将计算任务分配给计算单元,并为每个线程分配一些资源,例如寄存器、共享内存和常量内存等。然后,计算单元会同时启动多个线程束来执行指令序列。在执行过程中,GPU 可以动态地调整线程束的数量和分配的资源,以最大化并行计算的效率。

总的来说,线程束是 GPU 中执行并行计算任务的基本单元,它由一组线程组成,可以同时执行相同的指令序列。在软件端,程序员编写并行计算任务的代码,并将其编译成适合 GPU 的指令集。在硬件端,GPU 的计算单元可以同时执行大量的线程束,通过 SIMD 架构来高效地执行计算任务。

请添加图片描述

SIMD

在GPU中,SIMD是一种重要的线程分配策略。 当一个Kernel 函数被执行的时候,Grid中的线程块被分配到SM上。注意,一个SM可以调度多个线程块,但是同一个线程块内的所有线程只能在一个SM上。
每一个Thread拥有自己的程序计数器和状态寄存器,并且使用线程自带的数据执行同一个指令。这就被称作SIMD: Single Instruction Multi Data. SIMT的设计也是线程束是执行核函数最基本单元的原因。

四、CUDA编程实例:向量相加

现在假设我们希望通过并行计算完成下列函数:
请添加图片描述
可以看到这个函数访问内存少,控制简单,计算简单,并行度高,所以适合在GPU上运算
在GPU侧,需要完成的功能是:

  1. 读写线程寄存器
  2. 读写Grid中全局内存
  3. 读写Block中共享内存
    在CPU侧,需要完成的功能是:
  4. Grid中全局内存拷贝转移

需要涉及到的函数有:

  1. cudaMalloc( )
    • cudaError_t cudaMalloc (void **devPtr, size_t size)
    • 在设备全局内存中分配对象
    • 两个参数
    • 地址
    • 申请内存大小
  2. cudaFree( )
    • cudaError_t cudaFree ( void* devPtr ) • 从设备全局内存中释放对象
    • 指向释放对象的指针
  3. cudaMemcpy( )
    • cudaError_t cudaMemcpy (void *dst, const void *src, size_t count, cudaMemcpyKind kind)
    • 内存数据复制传递
    • 目前支持的四种选项
    • cudaMemcpyHostToDevice
    • cudaMemcpyDeviceToHost
    • cudaMemcpyDeviceToDevice
    • cudaMemcpyDefault
    • 调用cudaMemcpy( )传输内存是同步的

首先,我们可以先给出一个代码框架,写好除了kernel function以外的所有东西,随后再写kernel function

void vecAdd(float* A, float* B, float* C, int n)
{
int size = n * sizeof(float); 
float* A_d, *B_d, *C_d; 
1. // Transfer A and B to device memory 
cudaMalloc((void **) &A_d, size);
cudaMemcpy(A_d, A, size, cudaMemcpyHostToDevice);
cudaMalloc((void **) &B_d, size);
cudaMemcpy(B_d, B, size, cudaMemcpyHostToDevice);
// Allocate device memory for
cudaMalloc((void **) &C_d, size);
2. // Kernel invocation code – to be shown later3. // Transfer C from device to host
cudaMemcpy(C, C_d, size, cudaMemcpyDeviceToHost);
// Free device memory for A, B, C
cudaFree(A_d); cudaFree(B_d); cudaFree (C_d);
}

核函数调用
• 在GPU上执行的函数。
• 一般通过标识符__global__修饰。 • 调用通过<<<参数1,参数2>>>,用于说明内核函数中的线程数量,以及线程是如何组织的。
• 以网格(Grid)的形式组织,每个线程格由若干个线程块(block)组成,而每个线程块又由若干个线程(thread)组成。
• 调用时必须声明内核函数的执行参数。
• 在编程时,必须先为kernel函数中用到的数组或变量分配好足够的空间,再调用kernel函数,否则在GPU计算时会发生错误。

在CUDA编程中的标识符有这些:
请添加图片描述

/*
Device Code 
*/
__global__
void vecAddKernel(float* A_d, float* B_d, float* C_d, int n)
{
int i = threadIdx.x + blockDim.x * blockIdx.x;
if(i<n) C_d[i] = A_d[i] + B_d[i];
}

/*
HostCode 
*/
int vectAdd(float* A, float* B, float* C, int n)
{
// A_d, B_d, C_d allocations and copies omitted
// Run ceil(n/256) blocks, each thread block has 256 threads which has 8 thread warps   
vecAddKernel<<<ceil(n/256), 256>>>(A_d, B_d, C_d, n);
}

CUDA 编译流程请添加图片描述

五. 代码实例

在CPU上计算向量相加:

#include<bits/stdc++.h>
#include <sys/time.h>

using namespace std;
void vecAdd(float *A, float *B, float *C, int n){
    for(int i=0;i<n;i++){
        C[i] = A[i] + B[i];
    }
}
int main(int argc,char *argv[]){
  int n = atoi(argv[1]);
  cout<<n<<endl;
  size_t size = n * sizeof(float);
  float *a = (float *)malloc(size);
  float *b = (float *)malloc(size);
  float *c = (float *)malloc(size);

  for(int i=0;i<n;i++){
    float af = rand()/double(RAND_MAX);
    float bf = rand()/double(RAND_MAX);
    a[i]=af;
    b[i]=bf;
  }
  struct timeval t1,t2;
  gettimeofday(&t1,NULL);
  vecAdd(a,b,c,n);
  gettimeofday(&t2,NULL);
  double timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec)/1000000.0;
  cout<<"timeuse: "<<timeuse<<endl;
}

在GPU上计算向量相加:

#include<bits/stdc++.h>
#include<sys/time.h>
using namespace std;

__global__ 
void vecAddKernel(float *A, float *B, float *C, int n){
    int i = threadIdx.x + blockDim.x * blockIdx.x; 
    if(i<n) C[i] = A[i] + B[i]; 
}
int main(int argc,char *argv[]){
  int n = atoi(argv[1]);
  cout<<n<<endl;
  size_t size = n * sizeof(float);
  float *a = (float *)malloc(size);
  float *b = (float *)malloc(size);
  float *c = (float *)malloc(size);

  for(int i=0;i<n;i++){
    float af = rand()/double(RAND_MAX);
    float bf = rand()/double(RAND_MAX);
    a[i]=af;
    b[i]=bf;
  }
  float *da = NULL;
  float *db = NULL;
  float *dc = NULL;
  cudaMalloc((void **)&da,size);
  cudaMalloc((void **)&db,size);
  cudaMalloc((void **)&dc,size); 
  cudaMemcpy(da,a,size,cudaMemcpyHostToDevice);
  cudaMemcpy(db,b,size,cudaMemcpyHostToDevice);
  cudaMemcpy(dc,c,size,cudaMemcpyHostToDevice);
  int threadsPerBlock = 256;
  int blockPerGrid = (n + threadsPerBlock - 1) / threadsPerBlock;
  struct timeval t1,t2;
  gettimeofday(&t1,NULL);
  vecAddKernel<<<blockPerGrid,threadsPerBlock>>>(da,db,dc,n);
  cudaMemcpy(c,dc,size,cudaMemcpyDeviceToHost);
  gettimeofday(&t2,NULL);
  double timeuse = t2.tv_sec - t1.tv_sec + (t2.tv_usec - t1.tv_usec)/1000000.0;
  cout<<"timeuse: "<<timeuse<<endl;
  cudaFree(da);
  cudaFree(db);
  cudaFree(dc);
} 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/488642.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ModuleNotFoundError: No module named ‘allennlp‘解决方法

1. 准备工作 由于本人复现论文要安装allennlp的包但是遇到些问题&#xff0c;所以特地记录一下 需求:在SBN环境下安装allennlp的包 1.创建环境 创建一个环境名为SBN的环境名字 python版本为3.8.5 可以根据自己需求指定&#xff0c; conda create -n SBN python3.8.5conda a…

企业微信-构造网页授权链接实现登录

文档地址&#xff1a;构造网页授权链接 - 接口文档 - 企业微信开发者中心 注意&#xff1a; 1.redirect_uri&#xff1a;回调链接地址&#xff0c;需要使用urlencode对链接进行处理 2.scope&#xff1a;如果需要获取成员的头像、手机号等信息需要设为snsapi_privateinfo 例如前…

ChatGPT4.0火爆全球,是什么让它独领风骚?

ChatGPT造就了互联网历史上又一个神话&#xff0c;仅用两个月时间就成功吸引了1亿用户&#xff0c;成为全球互联网应用中增长速度最快的一个。连比尔盖茨都称&#xff1a;ChatGPT的历史意义重大&#xff0c;不亚于PC或互联网诞生。这个热度以至于ChatGPT官网长期都处于满负荷运…

射频功率放大器在S180肿瘤细胞膜研究中的应用

实验名称&#xff1a;聚焦超声对S180肿瘤细胞膜理化性质的影响 研究方向&#xff1a;生物医疗 测试目的&#xff1a; 细胞膜是细胞生命活动中有着复杂功能的重要结构其基本作用在于维持细胞内外环境的相对稳定而其通透性、完整性及流动性等理化性质则与胞内外信息传递、物质…

Unity——Mirror学习(01)

1.下载 Mirror是一个简单高效的开源的unity多人游戏网络框架&#xff0c;Mirror在Unity商店中是免费的&#xff0c;因此直接加入自己的资源库并在导入即可。 官方API地址&#xff1a;https://mirror-networking.gitbook.io/docs 2.使用 1.创建场景的网络管理器 网络管理器…

C#,生信软件实践(03)——DNA数据库GenBank格式详解及转为FASTA序列格式的源代码

1 GenBank 1.1 NCBI——美国国家生物技术信息中心&#xff08;美国国立生物技术信息中心&#xff09; NCBI&#xff08;美国国立生物技术信息中心&#xff09;是在NIH的国立医学图书馆&#xff08;NLM&#xff09;的一个分支。它的使命包括四项任务&#xff1a;1. 建立关于分…

Linux操作系统CentOS7安装Nginx[详细版]

Linux操作系统CentOS7安装Nginx[详细版] Nginx安装 1. 官网下载 Nginx 2. 使用 XShell 和 Xftp 将压缩包上传到 Linux 虚拟机中 3. 解压文件 nginx-1.20.2.tar.gz 4. 配置nginx 5. 启动 nginx 6. 拓展&#xff08;修改端口和常用命令&#xff09; &#xff08;一&#xff09;修…

QProgressBar详解

QProgressBar详解 [1] QProgressBar详解1.QProgressBar简述2.常用方法3.示例&#xff0c;比较进度条4.设置样式表 [1] QProgressBar详解 原文链接&#xff1a;https://blog.csdn.net/wzz953200463/article/details/125530997 1.QProgressBar简述 QProgressBar提供了一个水平…

Spark大数据处理讲课笔记3.5 RDD持久化机制

文章目录 零、本讲学习目标一、RDD持久化&#xff08;一&#xff09;引入持久化的必要性&#xff08;二&#xff09;案例演示持久化操作1、RDD的依赖关系图2、不采用持久化操作3、采用持久化操作 二、存储级别&#xff08;一&#xff09;持久化方法的参数&#xff08;二&#x…

docker使用集锦

docker简介 docker是一个软件&#xff0c;作用是在任意安装docker软件的系统上虚拟一个容器&#xff0c;用户可以在容器上构建任何自定义环境。 容器与虚拟机的区别在于&#xff0c;虚拟机需要对硬件也虚拟化&#xff0c;分配给虚拟机一定的资源&#xff0c;包括网卡&#xf…

SpringBoot热部署插件原理分析及实战演练

目录 1、关于热部署&#xff08;Hot Deploy&#xff09;产生的背景 1&#xff09;热部署出现前 2&#xff09;热部署出现后 2、spring-boot-devtools插件原理 1&#xff09;解决变更文件自动加载到JVM中 2&#xff09;spring-boot-devtools重启速度比手动重启快 3、关于…

最优化理论(一)Fibonacci法(python实现)

最优化理论之Fibonacci法(python实现) 纯纯干货 刚过完五一假期&#xff0c;又要回归到课程多还要兼顾每天考研复习的生活的日常了&#xff0c;这不&#xff0c;最优化理论课程又需要编写一些代码&#xff0c;鉴于网上基于python实现的系列算法很杂很散&#xff08;基本没有&…

juc--三大接口

文章目录 juc一、为什么会有juc二、juc--三大接口1.lock2.condition3.ReadWriteLock 二、juc--的默认实现类1.ReentrantLock--lock的默认实现类公平锁,非公平锁 2. ReentrantReadWriteLock读写锁--ReadWriteLock的默认实现类读写锁和排它锁 总结 juc juc: java.util.concurren…

IT行业比较吃香的技能有哪些?

前言 在互联网IT行业中&#xff0c;虽然只有短短几十年时间&#xff0c;但是技术革新一直都在&#xff0c;而且各类前沿技术层出不穷&#xff0c;不断迭代和创新。作为一个程序员&#xff0c;在互联网行业竞争很激烈的当下&#xff0c;更应该通过学习来应对日新月异的前沿技术。…

【IDEA Sprintboot】简单入门:整合SpringSecurity依赖、整合Thymeleaf框架

目录&#xff1a; 1、【IDEA】简单入门&#xff1a;请求数据库表数据_水w的博客-CSDN博客 目录 三、 1、整合SpringSecurity依赖 2、整合Thymeleaf框架 解决css样式等静态资源访问不到的问题 三、 1、整合SpringSecurity依赖 Spring Security是一个能够为基于Spring的企业…

正则表达式学习贴

1. 前言 1.1 为什么要学习正则表达式 1.1.1 极速体验正则表达式威力 /*** 体验正则表达式的威力&#xff0c;给我们文本处理带来哪些便利*/ public class Regexp_ {public static void main(String[] args) {//假定&#xff0c;编写了爬虫&#xff0c;从百度页面得到如下文本…

网络计算模式复习(二)

网格 由于B/S架构管理软件只安装在服务器端上&#xff0c;网络管理人员只需要管理服务器就行了&#xff0c;用户界面主要事务逻辑在服务器端完全通过WWW浏览器实现&#xff0c;极少部分事务逻辑在前端&#xff08;Browser&#xff09;实现&#xff0c;所有的客户端只有浏览器&…

李沐深度学习环境安装(包括pytorch和d2l)

李沐深度学习环境安装&#xff08;包括pytorch和d2l&#xff09; 目录一、安装Anaconda3二、安装GPU版本的pytorch三、使用jupyter notebook运行李沐书籍的源码 目录 一、安装Anaconda3 进入Anaconda官网下载&#xff1a;https://www.anaconda.com/distribution/ 安装细节不在…

小程序上车,车载小程序的信息安全是否可靠?

随着智能交通和车联网技术的快速发展&#xff0c;越来越多的车载应用程序&#xff08;APP&#xff09;进入人们的视野&#xff0c;从而推动了车载业务生态的不断发展。然而&#xff0c;车载应用程序的安全问题也引起了人们的广泛关注。为此&#xff0c;小程序容器技术作为一种有…

centos7.5 从0-1安装mysql以及基本的增删改查

系列文章目录 文章目录 系列文章目录前言一、mysql安装二、mysql客户端操作总结 前言 MySQL 是最流行的关系型数据库管理系统&#xff0c;在 WEB 应用方面 MySQL 是最好的 RDBMS(Relational Database Management System&#xff1a;关系数据库管理系统)应用软件之一。 什么是…