【并行计算】GPU,CUDA

一、CUDA层次结构

1.kernel核函数

一个CUDA程序是一个kernel核函数被GPU的多个计算单元并行执行的过程,CUDA给了如下抽象

dim3 threadsPerBlock(4, 3, 1);
dim3 numBlocks(3, 2, 1);
matrixAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);

 

2.Grid,Block,Thread

这样启动核函数,根据CUDA的抽象,就会有下面这样的运行模式,<<<>>>中间的两个参数numblocks和threadsPerBlock都是三维的变量,给予程序员设计的便利。

每个thread就是一个实际的核函数在运行,核函数可以根据当前的blockIdx,threadIdx来获得当前核函数所在的三维坐标位置。

int index = blockIdx.x * blockDim.x + threadIdx.x;

 

3.Streaming Multiprocessor(SM),warp

每个Block会分给一个SM(Streaming Multiprocessor),一个SM可以理解成一个有很多核的处理单元,并且有一个共享内存,下面看看一个SM内部如何工作。

下面这个图是一个典型的SM内部,每个黄方框都是一个SIMD单元,他们共享一个内存,左边的warp是实际分配给这些SIMD单元的任务,一个warp是一些线程的集合,CUDA用行优先的逻辑将一个block里的thread分配给warp,注意CUDA这里dim这个东西横纵坐标跟别的不太一样,如下图,他是Y是行号,X是列号。

在CUDA文档中,有讲到是根据线程id来顺序连续分配的,线程id计算方式如下

对于1维的来说,1维的x就是线程id

对于2维的来说,id是x + y Dx,y是行号,x是列号,所以就是行号乘一行的数量再加上列号。

对于3维的来说,id是x + y Dx + z Dx Dy,那就是高(z)乘上一个面的线程数,再加上y乘上行长在加上x。

所以总结来说,就是先分配面,然后在面上行优先分配。

一个warp通常是32个thread来执行SIMD指令,因为每个线程都是同样的核函数。但这里其实会有一个问题,那就是条件分支可能会不一样,最大的效率在这32个线程都执行相同的条件分支时达到,因为不同的分支会导致simd单元先执行一部分,而另一部分会等这部分执行完在执行。

所以一个warp才类似于操作系统中的一个线程,GPU会将warp视为线程来做硬件多线程调度。

看左边这一堆warp,存的就是每个warp的运行时状态,这里面包含了每个warp独立的寄存器、PC等东西,所以这里GPU做的硬件多线程就类似于一种超线程技术,使用多套上下文,使上下文切换没有开销。

二、CUDA内存层次结构

从最快的每个thread私有的内存,然后是整个块共享的一片内存,然后到整个GPU共享的全局内存。

一个值得注意的点,当一个warp访问内存中连续的地址时,会做块读取/写入,一次性将一个块内容读取/写入,所以如果让一个warp内的线程具有连续的内存访问模式,是比较好的,结合刚才的,如果也有同样的条件分支,那更好了。

三、一个矩阵乘法的优化例子

1.最基本的

直接A的行乘B的列相加,这会导致B的内存访问模式是跳跃的,不缓存友好。

2.预转置

那么就把B提前转置了,这样A和B都可以一行一行的访问了。

可以看到有一定的优化了

 

3.变成CUDA代码

最基础的版本,我们让C结果矩阵的每一个元素都用一个核函数来算结果,i和j就是C矩阵的i和j,我们直接将整个grid,映射成一个二维矩阵,那么横坐标i就是先拿块id的y乘上块的长度再加上块里面线程的横坐标y。纵坐标也类似。

___global__ void CUDASimpleKernel(int N, float *dmatA, float *dmatB, float *dmatC)
{
    int i = blockIdx.y * blockDim.y + threadIdx.y;
    int j = blockIdx.x * blockDim.x + threadIdx.x;
    if (i >= N || j >= N)
        return;
    float sum = 0.0;
    for (int k = 0; k < N; k++)
    {
        sum += dmatA[RM(i, k, N)] * dmatB[RM(k, j, N)];
    }
    dmatC[RM(i, j, N)] = sum;
}

然后i,j确定下来后,就去用k遍历A矩阵的一行和B矩阵的一列来计算结果元素。

当然,要变成CUDA代码还需要一些初始化的host代码。

首先要在GPU上分配内存,然后Memcpy过去

然后初始化块的数量和块的大小,就可以启动核函数了

然后算完之后再Memcpy回CPU

最后别忘了free掉GPU上用的内存

void CUDAMultMatrixSimple(int N, float *dmatA, float *dmatB, float *dmatC)
{
    dim3 threadsPerBlock(LBLK, LBLK);
    dim3 blocks(updiv(N, LBLK), updiv(N, LBLK));
    CUDASimpleKernel<<<blocks, threadsPerBlock>>>(N, dmatA, dmatB, dmatC);
}

void CUDAMultiply(int N, float *aData, float *bData, float *cData)
{
    float *aDevData, *bDevData, *cDevData;
    CUDAMalloc((void **)&aDevData, N * N * sizeof(float));
    CUDAMalloc((void **)&bDevData, N * N * sizeof(float));
    CUDAMalloc((void **)&cDevData, N * N * sizeof(float));
    CUDAMemcpy(aDevData, aData, N * N * sizeof(float), CUDAMemcpyHostToDevice);
    CUDAMemcpy(bDevData, bData, N * N * sizeof(float), CUDAMemcpyHostToDevice);

    CUDAMultMatrixSimple(N, aDevData, bDevData, cDevData);

    CUDAMemcpy(cData, cDevData, N * N * sizeof(float), CUDAMemcpyDeviceToHost);

    CUDAFree(aDevData);
    CUDAFree(bDevData);
    CUDAFree(cDevData);
}

好的,这有一个巨额的提升。

4. 考虑一个情况

刚才的i和j计算的代码变成这样,效果会变差十多倍。为什么呢

int i = blockIdx.x * blockDim.x + threadIdx.x;
int j = blockIdx.y * blockDim.y + threadIdx.y

想想内存访问模式的变化。刚才的代码,一个block里的一个warp是横着连续的,

横着连续说明他们的i一样,j连续,这说明,在对A矩阵的访问上,一直用的都是同一行,是内存中同一个连续的位置,可以进行块读。对B矩阵的访问上,是一列一列访问的,但是整个warp所需要访问的内存是连续的,所以也可以进行块读。

然后,对于写,是写C的连续的位置,因为是横着的,所以可以进行块写。

而新的代码

i是列号乘块的纵长,再加上块里的线程纵位置,也就是i和j对比刚才互换了,这样会导致什么,同一个warp里计算的是C矩阵纵向的元素。C矩阵纵向的元素,对于A,是不同的行,这样warp内整体也是连续的,可以进行块读,对于B,是同一列,这里读是不能块读的,因为内存是不连续的。

再看写,是竖着写的,所以写的也是C的不连续的位置,这样写也不能进行块写。

综上,这两个就差在一个块写和块读上了。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

git(安装,常用命令,分支操作,gitee,IDEA集成git,IDEA集成gitee,IDEA集成github,远程仓库操作)

文章目录 1. Git概述1.1 何为版本控制1.2 为什么需要版本控制1.3 版本控制工具1.4 Git简史1.5 Git工作机制1.6 Git和代码托管中心 2. Git安装3. Git常用命令3.1 设置用户签名3.1.1 说明3.1.2 语法3.1.3 案例实操 3.2 初始化本地库3.2.1 基本语法3.2.2 案例实操3.2.3 结果查看 3…

Innosetup 调用c# dll 和 c# dll的函数导出

目标需求&#xff0c;基于现在安装包脚本。需要在用户安装和卸载成功时。进行数据记录,所以需要调用c#dll 主要涉及到的知识点 需要理解脚本的文件使用机制脚本的文件dll加载&#xff0c;和dll的调用c# dll的制作&#xff0c;和工具的使用 下面具体介绍 脚本的文件dll加载&…

详解Vue3中的鼠标事件mousedown、mouseup和contextmenu

本文主要介绍Vue3中的常见鼠标事件mousedown、mouseup和contextmenu。 目录 一、mousedown——鼠标按下事件二、mouseup——鼠标弹起事件三、contextmenu——页面菜单 下面是Vue 3中常用的鼠标事件mousedown、mouseup和contextmenu的详解。 一、mousedown——鼠标按下事件 mo…

CloneNotSupportedException的解决方案 + Object的clone方法分析

CloneNotSupportedException的解决方案 引入问题&#xff1a; 在一次测试clone方法时&#xff0c;D类Override了Object类的clone方法 public class D {private Integer A1;private Integer A2;public D() {}public D(Integer a1, Integer a2 {A1 a1;A2 a2;}Overrideprotec…

PAT 乙级 1042 字符统计

请编写程序&#xff0c;找出一段给定文字中出现最频繁的那个英文字母。 输入格式&#xff1a; 输入在一行中给出一个长度不超过 1000 的字符串。字符串由 ASCII 码表中任意可见字符及空格组成&#xff0c;至少包含 1 个英文字母&#xff0c;以回车结束&#xff08;回车不算在内…

软件测试/测试开发丨Python 内置库 sys 学习笔记分享

sys 概述 是 Python 自带的内置模块是与 Python 解释器交互的桥梁 sys 使用 常用属性常用方法导入 sys 模块 # 导入sys模块 import sys# 查看sys模块帮助文档 help(sys)# 查看sys模块的属性和方法 print(dir(sys))sys 常用属性 sys.version&#xff1a;返回 Python 解释器…

Linux基础知识学习2

tree命令的使用 可以看到dir2目录下的这些文件&#xff0c;要想显示dir2的具体结构&#xff0c;可用tree命令 mv命令 它可以实现两个功能 1.将文件移动到另一个目录中 2.对某一个文件进行重命名 1.将文件移动到另一个目录中 这里将dir1中的2.txt移动到他的子目录dir3中 执行…

虚拟化分类和实现原理

6、虚拟化分类 &#xff08;1&#xff09;完全虚拟化 直接将Hypervisor跑在0环内核态&#xff0c;客户机os跑在1环&#xff0c;一旦触发敏感指令&#xff0c;由0环的VMM进行捕获翻译&#xff0c;从而模 拟这些指令。而运行在1环的GuestOS永远都不知道自己是个虚拟机。是完全…

python统计分析——透视表

参考资料&#xff1a;用Python动手学统计学 pandas库的pivot_table函数相当于excel的透视表功能。此图为excel数据透视表字段设置窗口&#xff0c;下面将参照excel数据透视表相关设置图片学习pivot_table函数&#xff1a; 本次使用的数据集内容如下&#xff1a; import pandas…

python安装MongoDB与运算符优先级

python安装MongoDB MongoDB 是目前最流行的 NoSQL 数据库之一&#xff0c;使用的数据类型 BSON&#xff08;类似 JSON&#xff09;。 PyMongo Python 要连接 MongoDB 需要 MongoDB 驱动&#xff0c;这里我们使用 PyMongo 驱动来连接。 pip 安装 pip 是一个通用的 Python 包…

【STM32】STM32学习笔记-PWM驱动LED呼吸灯 舵机 直流电机(16)

00. 目录 文章目录 00. 目录01. 输出比较相关API1.1 TIM_OC1Init1.2 TIM_OCInitTypeDef结构体1.3 TIM_OCMode1.4 TIM_OutputState1.5 TIM_OutputNState1.6 TIM_OCPolarity1.7 TIM_OCNPolarity1.8 TIM_OCPolarity1.9 TIM_OCNPolarity 02. PWM实现呼吸灯接线图03. PWM实现呼吸灯示…

普中STM32-PZ6806L开发板(前序)

前言 突然从柜子看到七八年前买的一块普中开发板, 在诸多的例如野火、原子中当时为什么选择他, 现在来看应该还是性价比较高&#xff0c;班上集成了很多学习者的进阶模块了&#xff0c;当然&#xff0c;买完大程度就吃灰了&#xff0c;当我再次发现他的时候&#xff0c; 我看到…

独立站的营销策略:吸引顾客的秘密武器

一、独立站的重要性 独立站是指企业自主建立的电子商务网站&#xff0c;具有独立的域名和运营管理权。通过独立站&#xff0c;企业可以展示产品信息、提供在线服务、进行促销活动等&#xff0c;与顾客建立互动和信任关系。独立站的重要性在于它可以帮助企业建立品牌认知度、提…

【NTN 卫星通信】Oneweb星座以及Oneweb与Starlink比较

1 什么是OneWeb OneWeb于2012年以WorldVu的名义成立&#xff0c;于2020年开始构建其星座。然而&#xff0c;对于这家英国公司来说&#xff0c;这是一个艰难的旅程&#xff0c;OneWeb于2020年3月宣布破产&#xff0c;并认为covid-19大流行是一个主要因素。OneWeb星座当时仅完成…

trino-435: 理论基础

一、trino介绍 Trino是⼀种⽀持使⽤ SQL 访问任意数据源的 开源的分布式SQL 查询引擎&#xff0c;其能够提供更加灵活与⾼效的查询服务。为不同的异构数据源提供统⼀的sql访问&#xff0c;并⽀持联邦查询和并⾏查询。 应⽤场景 Trino是定位在数据仓库和数据分析业务的分布式S…

#前后端分离# 头条发布系统

头条业务简介 新闻的分页浏览通过标题关键字搜索新闻查看新闻详情新闻的修改和删除用户注册、登录 预览界面 开源上线 https://gitcode.net/NVG_Haru/NodeJS_5161447 数据库设计 数据库脚本 CREATE DATABASE sm_db;USE sm_db;SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0…

python的pywebio库给孩子做加减法数学题

效果展示 程序执行后&#xff0c;打开浏览器&#xff0c;展示一些100以内的加减法混合运算的数学题并输入答案后判断对错&#xff0c;这样倒是省了买教材的钱了。 在题目下方的框中&#xff0c;输入答案&#xff0c;然后点击提交后&#xff0c; 会输出结果 pywebio库介绍 安装…

详解FreeRTOS:FreeRTOSConfig.h系统配置文件(拓展篇—1)

目录 1、“INCLUDE_”宏 2、“config”宏 实际使用FreeRTOS的时候,时常需要根据自己需求来配置 FreeRTOS,不同架构的MCU,配置也不同。 FreeRTOS的系统配置文件为FreeRTOSConfig.h,在配置文件中可以完成FreeRTOS的裁剪和配置,这是非常重要的一个文件,本篇博文就来讲解这…

文件批量整理,文件归类整理,文件批量归类

我们每天都要面对无数的文件&#xff0c;从工作报告、个人照片到电影和音乐。如何有效地管理和归类这些文件&#xff0c;成为了我们日常生活和工作中所要处理的。今天&#xff0c;小编就给大家介绍一款简单易用的工具——文件批量改名高手&#xff0c;助你轻松实现文件批量归类…

45、激活函数 - 为什么非线性这么重要

这一节开始讲一讲神经网络中的激活函数,在讲激活函数之前,先讲一下非线性。 看一个基础知识:线性函数的叠加,我们初中学过的知识点。 假设有一个线性函数,y = kx + b, 这个函数画出来是下面的样子,这里显示 y 和 x 是线性关系。 而如果这个时候又有一个线性关系 z = hy…