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

本文来源公众号“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; // 线程IDif (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.mzph.cn/web/15089.shtml

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

相关文章

Linux之共享内存mmap用法实例(六十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

外卖霸王餐返利外卖会员卡小程序开发

外卖霸王餐返利外卖会员卡小程序开发 "社交电商赋能下的外卖返利小程序"是专为商家与用户双赢而设计的创新平台。 以下是其开发方案的详细步骤&#xff1a; 一、需求梳理&#xff1a;首先&#xff0c;我们需要明确小程序的核心功能和特色。包括设定活动类型、返利…

【Docker】Linux 系统(CentOS 7)安装 Docker

文章目录 对 VMware 软件的建议官方说明文档Docker安装卸载旧版本docker设置仓库开始安装 docker 引擎最新版 Docker 安装指定版本 Docker 安装&#xff08;特殊需求使用&#xff09; 启动 Docker查看 Docker 版本查看 Docker 镜像设置 Docker 开机自启动 验证开机启动是否生效…

自定义原生小程序顶部及获取胶囊信息

需求&#xff1a;我需要将某个文字或者按钮放置在小程序顶部位置 思路&#xff1a;根据获取到的顶部信息来定义我需要放的这个元素样式 * 这里我是定义某个指定页面 json&#xff1a;给指定页面的json中添加自定义设置 "navigationStyle": "custom" JS&am…

新时代AI浪潮下,程序员和产品经理如何入局AIGC领域?

当下&#xff0c;AI浪潮席卷全球&#xff0c;AIGC大模型技术已经成为当今技术领域的一个重要趋势&#xff0c;对于产品经理来说&#xff0c;掌握这项技术不仅能够增强他们的职业技能&#xff0c;还能在竞争激烈的职场中脱颖而出。 为什么呢&#xff1f; 把握AI时代的机遇 AI技…

StringMVC

目录 一&#xff0c;MVC定义 二&#xff0c;SpringMVC的基本使用 2.1建立连接 - RequestMapping("/...") ​编辑 2.2请求 1.传递单个参数 2.传递多个参数 3.传递对象 4.参数重命名 5.传递数组 6. 传递集合 7.传递JSON数据 8. 获取url中数据 9. 传递文…

python数据类型之元组、集合和字典

目录 0.三者主要作用 1.元组 元组特点 创建元组 元组解包 可变和不可变元素元组 2.集合 集合特点 创建集合 集合元素要求 集合方法 访问与修改 子集和超集 相等性判断 集合运算 不可变集合 3.字典 字典特点 字典创建和常见操作 字典内置方法 pprin模块 0.…

k8s——Pod详解

一、Pod基础概念 1.1 Pod定义 Pod是kubernetes中最小的资源管理组件&#xff0c;Pod也是最小化运行容器化应用的资源对象。一个Pod代表着集群中运行的一个进程。kubernetes中其他大多数组件都是围绕着Pod来进行支撑和扩展Pod功能的&#xff0c;例如&#xff0c;用于管理Pod运行…

Java进阶学习笔记7——权限修饰符

什么是权限修饰符&#xff1f; 就是用来限制类中的成员&#xff08;成员变量、成员方法、构造器、代码块....&#xff09;能够被访问的范围。 protected使用的比较少&#xff0c;但是程序员还是要阅读代码&#xff0c;看官方文档是怎么写的&#xff0c;都会接触到protected修饰…

性能测试场景的设计方法

引用&#xff1a;根据2008年Aberdeen Group的研究报告&#xff0c;对于Web网站&#xff0c;1秒的页面加载延迟相当于少了11%的PV&#xff08;page view&#xff09;&#xff0c;相当于降低了16%的顾客满意度。如果从金钱的角度计算&#xff0c;就意味着&#xff1a;如果一个网站…

「探讨」:什么是网络审计?好用的网络审计系统推荐【图文详解】

网络是企业运营、政府管理、个人生活不可或缺的基础设施。 然而网络安全问题却日益凸显&#xff0c;数据泄露、网络攻击、欺诈行为等风险日益严重。 一、网络审计的定义 网络审计&#xff0c;又称信息技术审计或电子审计&#xff0c;是指审计人员运用专业技能和工具&#xff…

实战之快速完成 ChatGLM3-6B 在 GPU-8G的 INT4 量化和本地部署

ChatGLM3 (ChatGLM3-6B) 项目地址 https://github.com/THUDM/ChatGLM3大模型是很吃CPU和显卡的&#xff0c;所以&#xff0c;要不有一个好的CPU&#xff0c;要不有一块好的显卡&#xff0c;显卡尽量13G&#xff0c;内存基本要32GB。 清华大模型分为三种(ChatGLM3-6B-Base&…

“大数据建模、分析、挖掘技术应用研修班”的通知!

随着2015年9月国务院发布了《关于印发促进大数据发展行动纲要的通知》&#xff0c;各类型数据呈现出了指数级增长&#xff0c;数据成了每个组织的命脉。今天所产生的数据比过去几年所产生的数据大好几个数量级&#xff0c;企业有了能够轻松访问和分析数据以提高性能的新机会&am…

Vue3中为Ant Design Vue中table的checkbox加tooltip、popover

问题的产生 Vue版本&#xff1a;3.3.13 ant-design-vue 版本&#xff1a;3.x.x 在工作时遇到一个场景&#xff0c;需要在 ant-table 的 checkbox 被禁用的时候提示原因&#xff0c;但是在 ant-design-vue 文档中并没有发现有相关介绍。 首先我去看了issue中是否有提到相关问题…

【金砖赛】基于docker搭链

一&#xff0e;基于docker搭建 启动并检查是否启动成功 #启动命令 sudo systemctl start docker#检查命令 sudo systemctl status docker修改权限 chomd 777 build_chain.sh 进行4节点联盟链搭建&#xff08;举例&#xff09; sudo ./build_chain.sh -d -l "127.0.0.1…

基于Android Studio记事本系统

目录 项目介绍 图片展示 运行环境 获取方式 项目介绍 具有登录&#xff0c;注册&#xff0c;记住密码&#xff0c;自动登录的功能&#xff1b; 可以新增记事本&#xff0c;编辑&#xff0c;删除记事本信息&#xff0c;同时可以设置主标题&#xff0c;内容&#xff0c;以及…

Ansible自动化运维中的Setup收集模块应用详解

作者主页&#xff1a;点击&#xff01; Ansible专栏&#xff1a;点击&#xff01; 创作时间&#xff1a;2024年5月22日13点14分 &#x1f4af;趣站推荐&#x1f4af; 前些天发现了一个巨牛的&#x1f916;人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xf…

服务器没有图形界面没有显示器怎么办

可以用vnc。 vnc是开元的。什么是vnc&#xff1f; 使用vnc 下载vnc和vncserver命令。 每生成一个图形界面就叫做开启session会话。 vnc相关命令&#xff1a; start a new session: vncserver。 如果没有会话&#xff0c;一般从:1开始 端口5901 vncserver :2 #指定会话为:2 端…

WinForm+SQL Server+.NET开发菜鸟驿站管理系统

完整效果看哔哩哔哩&#xff0c;有需要其他系统&#xff0c;可以私信

Leecode热题100---55:跳跃游戏(贪心算法)

题目&#xff1a; 给你一个非负整数数组 nums &#xff0c;你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标&#xff0c;如果可以&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 贪心算…