DeepDriving | CUDA编程-03:线程层级

news2024/12/29 9:20:23

本文来源公众号“DeepDriving”,仅用于学术分享,侵权删,干货满满。

原文链接:CUDA编程-03:线程层级

DeepDriving | CUDA编程-01: 搭建CUDA编程环境-CSDN博客

DeepDriving | CUDA编程-02: 初识CUDA编程-CSDN博客

1 GPU架构概述

英伟达GPU的架构是围绕一个流式多处理器(Streaming Multiprocessors,SM)的可扩展阵列构建的,通过复制这种架构的构建来实现GPU的硬件并行。一个典型的SM包括以下几个组件:

  • 核心

  • 共享内存/一级缓存

  • 寄存器文件

  • 加载/存储单元

  • 特殊功能单元

  • 线程束调度器

一个GPU中通常有多个SM,每个SM上支持许多个线程并发地执行,CUDA采用单指令多线程(Single-Instruction Multiple-Thread,SIMT)来管理和执行GPU上的众多线程,并提出一个两级的线程层级结构的概念以便组织线程。由一个内核启动所产生的所有线程统称为一个线程网格,同一网格中的所有线程共享全局内存空间,一个网格由多个线程块组成,一个线程块包含一组线程,同一线程块内的线程通过同步和共享内存的方式实现协作,不同块内的线程不能协作。当host通过内核函数启动一个内核网格时,这个内核网格的线程块就被分配到可用的SM上来执行,一个线程块内的多个线程在SM上并发执行,多个线程块可以并发地在一个SM上执行,当线程块终止时,新的线程块又可以在腾出的SM上启动执行。

2 线程

线程是并行程序的基础,并行化的方式一般有两种:任务并行和数据并行。任务并行是将一个计算任务分解为几个子任务,通过不同的线程分别执行各个子任务,最后汇总结果;数据并行是将一个总任务在数据粒度上进行划分,然后每个线程处理一份数据,每个线程上执行的计算任务是一样的。

举个搬砖的例子:

假设我们的任务是将100个砖从A点搬到B点,搬砖的任务分为3个子任务:把砖从A点装车、从A点运送到B点、在B点把砖从车上卸下来。如果采用任务并行方式,那么可以请多个工人,然后把他们分为3个组,每个组负责一个子任务 ;如果是采用数据并行,那么可以请100个工人,每个人负责1个砖,每个人的任务都是把砖从A点搬到B点。

GPU采用数据并行的模式,它可以运行成千上万的线程用于运行大量逻辑比较简单的计算任务以实现高效的并行化计算。在上一篇文章中,我介绍了一个数组相加的例子,本文继续以这个例子来介绍GPU中以多线程实现并行化的方式。

先来看一下CPU实现数组相加的方式:

void VectorAddCPU(const float *const a, const float *const b, float *const c,
                  const int n) {
  for (int i = 0; i < n; ++i) {
    c[i] = a[i] + b[i];
  }
}

CPU的代码默认是单线程执行模式,要想实现含多个数据的数组相加任务,就必须以循环的方式实现(相当于一个人要把所有的砖搬完)。

再来看GPU的实现方式:

__global__ void VectorAddGPU(const float *const a, const float *const b,
                             float *const c, const int n) {
  int i = blockDim.x * blockIdx.x + threadIdx.x; // 线程ID
  if (i < n) {
    c[i] = a[i] + b[i]; //每个线程需要做的事情
  }
}

可以看到,GPU代码中并不需要循环,只是需要一个线程ID来进行索引,并告诉每个线程需要做的事情。线程依靠两个内置变量来进行区分:

  • blockIdx: 线程块在线程网格中的索引

  • threadIdx: 线程块内的线程索引

这两个CUDA内置变量是基于uint3定义的向量类型,是一个包含x,y,z三个无符号整数字段的结构。

在调用内核函数的时候,会在<<< >>>内设置两个参数,分别代表线程网格的维度和线程块的维度。CUDA可以组织三维的线程网格和线程块,它们的维度由下列两个内置变量来决定:

  • blockDim: 线程块的维度,用每个线程块中的线程数量来表示

  • gridDim: 线程网格的维度,用每个线程网格中的线程块数量来表示

它们是基于uint3定义的dim3结构类型的变量,用于表示维度,每个维度可通过x,y,z字段获得,未被初始化的字段会被初始化为1且忽略不计。通常情况下,一个线程网格会被组织成线程块的二维数组形式,一个线程块会被组织成线程的三维数组形式。

const size_t size = 1024;
dim3 thread_per_block(256);
dim3 block_per_grid((size + thread_per_block.x - 1) / thread_per_block.x);
printf("thread_per_block: %d, block_per_grid: %d \n", thread_per_block.x,
        block_per_grid.x);
VectorAddGPU<<<block_per_grid, thread_per_block>>>(da, db, dc, size);

在上面的例子中,我只初始化了线程网格和线程块的第一维x,相当于设定线程网格中的线程块是以一维的形式排列,每个线程块中的线程也是以一维的形式排列,在内核函数中每个线程的ID可以这样得到:

const unsigned int id = blockDim.x * blockIdx.x + threadIdx.x; 

我们可以在内核函数中打印gridDim,blockDim,blockIdx,threadIdx这些信息看一下:

......
gridDim:(4 1 1), blockDim:(256 1 1), blockIdx:(1 0 0), threadIdx:(29 0 0)
gridDim:(4 1 1), blockDim:(256 1 1), blockIdx:(1 0 0), threadIdx:(30 0 0)
gridDim:(4 1 1), blockDim:(256 1 1), blockIdx:(1 0 0), threadIdx:(31 0 0)
gridDim:(4 1 1), blockDim:(256 1 1), blockIdx:(0 0 0), threadIdx:(0 0 0)
gridDim:(4 1 1), blockDim:(256 1 1), blockIdx:(0 0 0), threadIdx:(1 0 0)
gridDim:(4 1 1), blockDim:(256 1 1), blockIdx:(0 0 0), threadIdx:(2 0 0)
......

thread_per_block设置为512再看一下:

......
gridDim:(2 1 1), blockDim:(512 1 1), blockIdx:(1 0 0), threadIdx:(93 0 0)
gridDim:(2 1 1), blockDim:(512 1 1), blockIdx:(1 0 0), threadIdx:(94 0 0)
gridDim:(2 1 1), blockDim:(512 1 1), blockIdx:(1 0 0), threadIdx:(95 0 0)
gridDim:(2 1 1), blockDim:(512 1 1), blockIdx:(0 0 0), threadIdx:(416 0 0)
gridDim:(2 1 1), blockDim:(512 1 1), blockIdx:(0 0 0), threadIdx:(417 0 0)
gridDim:(2 1 1), blockDim:(512 1 1), blockIdx:(0 0 0), threadIdx:(418 0 0)
......

可以看到,启动内核函数的时候在<<< >>>内设置不同的执行参数,内核中线程的布局是不一样的。

3 线程束

CUDA采用SIMT架构来管理和执行线程,将线程块中的线程每32个(记住这个神奇的数字)为一组进行划分,每一组被称为一个线程束(warp)。线程束的大小warpSizeCUDA中的一个内部属性,可以通过以下方式获得:

cudaDeviceProp prop;
cudaGetDeviceProperties(&prop, 0);
printf("warpSize: %d\n", prop.warpSize);

线程束是GPU的基本执行单元,当线程网格启动后,网格中的线程块被分配到SM中执行,一旦线程块被调度到一个SM上,线程块中的线程就会被进一步划分为线程束,每个线程束中的所有线程执行相同的命令,每个线程拥有自己的指令地址计数器和寄存器状态,利用自己的私有数据执行当前的指令。线程块的逻辑视图和硬件视图之间的关系如下:

从逻辑角度看,线程块是线程的集合,它们可以被组织成一维、二维或者三维的布局形式;从硬件角度来看,线程块是一维线程束的集合,线程块中的线程被组织成一维布局,每32个连续的线程组成了一个线程束。

由于在硬件上线程块中的线程会被划分为线程束,而线程束不会在不同线程块之间分离,也就是说同一个线程束中的线程不会同属于两个线程块。如果线程块的大小不是线程束大小的偶数倍,那么最后一个线程束里就会有些线程没有用,但是它们依然会消耗SM的资源,所以在设置线程块大小的时候,最好设置为32的倍数。下图展示了一个线程块中包含80个线程时的情况,硬件为这些线程分配了3个线程束,最后一个线程束中有些线程是没有用的。

4 线程块

对于一份给定的数据,确定网格和块的维度的一般步骤为:

  1. 确定块的维度大小;

  2. 在已知数据大小和块大小的基础上计算网格的维度。

如何确定一个块的维度大小,通常需要考虑内核的性能特性和GPU的资源限制,比如寄存器和共享内存的大小,使用合适的网格和块大小来组织线程可以对内核性能产生较大的影响。在程序中,应该尽量避免使用小的线程块,因为这样无法充分利用硬件资源。为了防止不合理的内存合并,我们需要尽量做到数据内存的分布与线程的分布达到一一映射的关系。CUDA的设计思想是将数据分解到并行的线程和线程块中,使得程序结构与内存数据的分布能够建立一一映射的关系。假如我们需要计算二维数组的相加,那么可以将线程网格和线程块划分为二维:

这种情况下计算线程的ID会稍微复杂一点,首先计算当前的行索引,然后乘以每一行的线程总数,最后加上X轴方向上的偏移,这样就能计算出线程相对于整个线程网格的绝对线程索引:

const unsigned int idx = blockDim.x * blockIdx.x + threadIdx.x;
const unsigned int idy = blockDim.y * blockIdx.y + threadIdx.y;
const unsigned int thread_id = (gridDim.x * blockDim.x) * idy + idx;

当然,二维线程块的布局方式也有多种,比如下面这两种,它们的线程总数是一样的,但左图的布局要比右图的更高效。因为无论是在CPU还是在GPU中都是以行的方式进行内存访问,以右图的布局方式,同一行的数据需要被2个线程块访问2次,而左图的布局同一行的数据只需要访问1次即可。

5 参考资料

  • Professional CUDA C Programming

  • CUDA C Programming Guide

  • CUDA Programming:A Developer's Guide to Parallel Computing with GPUs

THE END !

文章结束,感谢阅读。您的点赞,收藏,评论是我继续更新的动力。大家有推荐的公众号可以评论区留言,共同学习,一起进步。

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

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

相关文章

操作系统总结3

目录 2.3.3 进程互斥的硬件的实现方法 &#xff08;1&#xff09;知识总览 &#xff08;2&#xff09;中断的屏蔽方法 &#xff08;3&#xff09;TestAndSet指令 &#xff08;4&#xff09;Swap指令 总结 2.3.4 信号量机制 &#xff08;1&#xff09;知识总览 &#x…

IO游戏设计思路

1、TCP ,UDP ,KCP ,QUIC TCP 协议最常用的协议 UDP协议非常规的协议&#xff0c;因为需要在线广播&#xff0c;貌似运营商会有一些影响 KCP 基于UDP的协议&#xff0c;GitHub - l42111996/java-Kcp: 基于java的netty实现的可靠udp网络库(kcp算法)&#xff0c;包含fec实现&am…

50.WEB渗透测试-信息收集-CDN识别绕过(3)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;49.WEB渗透测试-信息收集-CDN识别绕过&#xff08;2&#xff09; 关于cdn的识别方法内容…

智享无人直播系统(三代)融合AI智能互动,成就无人直播行业的新巨星!

随着直播行业的不断发展&#xff0c;智享直播&#xff08;三代&#xff09;作为首家自主研发的智能AI直播软件引领了行业的新潮流。相比市场上的其他同类软件&#xff0c;我们的软件通过创新的功能实现了直播间的AI智能互动&#xff0c;提供了丰富而个性化的直播体验。最重要的…

坚守互联网底层逻辑,搜狐走向长期主义的next level

2024年以来&#xff0c;随着我国经济回升向好态势进一步巩固增强&#xff0c;网络内容供给不断丰富&#xff0c;新型消费持续活跃&#xff0c;互联网板块整体估值向预期进行修正。因此&#xff0c;中概互联网指数ETF&#xff08;KWEB&#xff09;一转颓势&#xff0c;截至5月21…

拼多多:电商卷王

618大促将至&#xff0c;你还在天天算怎么用券吗&#xff1f;各电商卷来卷去&#xff0c;但可能都卷不过“天天618”的——拼多多。 5月22日&#xff0c;拼多多公布Q1财报&#xff0c; 营收同比增长131%至868.1亿人民币&#xff0c;调整后净利润同比增长202%至306.0亿元人民币…

Vue从入门到实战Day11

一、为什么要学Vue3 Vue3官网&#xff1a;简介 | Vue.js 1. Vue3的优势 2. Vue2选项式API vs Vue3组合式API 示例&#xff1a; 二、create-vue搭建Vue3项目 1. 认识create-vue create-vue是Vue官方新的脚手架工具&#xff0c;底层切换到了vite(下一代构建工具)&#xff0c;为…

【Vue2.x】props技术详解

1.什么是prop&#xff1f; 定义&#xff1a;组件标签上注册的一些自定义属性作用&#xff1a;向子组件传递数据特点 可以传递任意数量的prop可以传递任意类型的prop 2.prop校验 为了避免乱传数据&#xff0c;需要进行校验 完整写法 将之前props数组的写法&#xff0c;改为对象…

【搜索】BFS

#include <iostream> #include <cstring> #include <queue>using namespace std;const int N 110;typedef pair<int, int> PII;int n, m; int g[N][N], d[N][N];//存放地图//存每一个点到起点的距离int bfs() {queue< PII > q;q.push({0, 0});m…

Docker技术搭建Grafana监控平台

centos7虚拟机和docker的安装&#xff1a;可以参考之前的博文 CPU、mysql-exporter、docker监控模板&#xff1a;百度网盘 提取码&#xff1a;0000 先查看服务器时间是否和当前时间一致&#xff0c;如果不一致&#xff0c;查看对应设置&#xff1a;centos7时间同步博文 一、…

计网期末复习指南:万字总结计算机网络体系结构 | 计算机网络的组成、类别、性能

目录 一.互联网的发展阶段 二.计算机网络的类别 三.互联网的组成 ▐ 网络边缘部分 客户/服务器方式&#xff08;C/S方式&#xff09; 对等方式&#xff08;P2P方式&#xff09; ▐ 网络核心部分 电路交换 报文交换 分组交换 四.计算机网络的性能 ▐ 速率 ▐ 带宽 …

错误0xc0000022的3种解决方法

程序无法正常启动&#xff0c;报错代码为0xc0000022。当你的电脑运行程序出现这种情形&#xff0c;多半是由于系统的权限问题引起的。 原因一&#xff1a;应用程序的访问权限不足 有时候&#xff0c;直接打开文件时会遇到“0xc0000022” 错误&#xff0c;但是右键“以管理员身份…

深入解析力扣161题:相隔为 1 的编辑距离(逐字符比较与动态规划详解)

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

手把手教你搭建一个花店小程序商城

如果你是一位花店店主&#xff0c;想要为你的生意搭建一个精美的小程序商城&#xff0c;以下是你将遵循的五个步骤。 步骤1&#xff1a;登录乔拓云平台进入后台 首先&#xff0c;你需要登录乔拓云平台的后台管理页面。你可以在电脑或移动设备上的浏览器中输入乔拓云的官方网站…

STM32使用旋转编码开关

一、旋转编码开关如何工作 编码器内部有一个开槽圆盘&#xff0c;连接到公共接地引脚 C。它还具有两个接触针 A 和 B&#xff0c;如下所示。 当您转动旋钮时&#xff0c;A 和 B 按照特定顺序与公共接地引脚 C 接触&#xff0c;具体顺序取决于转动旋钮的方向。 当它们与公共地接…

VSCode SAP Systems配置HTTPS访问SAP

第一次访问提示&#xff0c;Self-Signed 证书 解决办法&#xff1a;https访问SAP Fiori网站&#xff0c;导出SSL证书为DER格式保存到硬盘上 双击DER文件&#xff0c;导入到系统 退出VSCode&#xff0c;再次启动 Test Connection, 提示 The system URL is using a hostname o…

计算机系统概述习题

选择题 电子计算机问世至今&#xff0c;新型计算机不断推陈出新&#xff0c;不管怎么更新&#xff0c;依然具有“存储程序”的特点&#xff0c;最早提出这种概念的是(B) A. 巴贝奇 B. 冯*诺伊曼 C. 帕斯卡 D. 贝尔 B下列描述中___是正确的。 A. 控制器能理解&#xff0c;解释…

【C语言深度解剖】(17):预处理和程序环境

&#x1f921;博客主页&#xff1a;醉竺 &#x1f970;本文专栏&#xff1a;《C语言深度解剖》 &#x1f63b;欢迎关注&#xff1a;感谢大家的点赞评论关注&#xff0c;祝您学有所成&#xff01; ✨✨&#x1f49c;&#x1f49b;想要学习更多C语言深度解剖点击专栏链接查看&…

gpt-4o继续迭代考场安排程序 一键生成考场清单

接上两篇gpt-4o考场安排-CSDN博客&#xff0c;考场分层次安排&#xff0c;最终exe版-CSDN博客 当然你也可以只看这一篇。 今天又添加了以下功能&#xff0c;程序见后。 1、自动分页&#xff0c;每个考场打印一页 2、添加了打印试场单页眉 3、添加了页脚 第X页&#xff0c;…

tomcat--安全配置多虚拟机

端口8005/tcp 安全配置管理 8005是Tomcat的管理端口&#xff0c;默认监听在127.0.0.1上。无需验证就可发送SHUTDOWN (大小写敏感)这个字符串&#xff0c;tomcat接收到后就会关闭此Server。此管理功能建议禁用&#xff0c;可将SHUTDOWN改为一串猜不出的字符串实现或者port修改成…