[5] CUDA线程调用与存储器架构

CUDA线程调用与存储器架构

  • 前几节简单讲了如何编写CUDA程序,利用GPU的处理能力并行执行多个线程和块。
  • 之前所有程序里的线程是相互独立的,没有多个线程之间的通信
  • 多是实际应用程序需要中间线程之间的通信,本文将仔细讲解线程调用以及CUDA的分层存储架构,以及加速CUDA代码是使用不同存储器之间的区别。

1. 线程调用

  • CUDA 关于并行执行具有分层结构。每次内核启动时可以被切分成多个并行执行的块,而每个块又可以进一步的被切分成多个线程
  • GPU 中 1个块中的线程可以相互通信,即启动 1 个具有多个线程的块让里面的线程能够相互通信是一个优势
  • 最大的块能有1024个线程,但是我们在执行程序时需要开启所有线程吗?答案时不一定的,可以同时启动 多个块+块中的多个线程。假设一个向量加法例子需要启动 N=50000 这么多的线程,可以这样调用内核:
//如果块数量 N 不是512的倍数,需要加上511,然后再除以512
gpu<< <(N + 511) /512), 512 > >>(d_a,d_b,d_c)

1.1 向量加法

  • 简单描述一下GPU的结构,众所周知它是由很多块组成的计算单元,它的形态可以看作好多个魔方(立方体)拼接而成的结构,因此每个块可以通过x,y,z三个方向来确定它的位置或者id,下边举例说明一个通过启动 x 方向多个块 和 块中多个线程来实现向量加法,并给出详细分析
#include "stdio.h"
#include<iostream>
#include <cuda.h>
#include <cuda_runtime.h>
//Defining number of elements in Array
#define N	50000
//Defining Kernel function for vector addition
__global__ void gpuAdd(int *d_a, int *d_b, int *d_c) {//Getting block index of current kernelint tid = threadIdx.x + blockIdx.x * blockDim.x;	while (tid < N){d_c[tid] = d_a[tid] + d_b[tid];tid += blockDim.x * gridDim.x;}}
  • 上述先给出了核函数,与之前程序不同的是tid的计算方式,即 tid = blockIdx.x(当前块的ID) * blockDim.x(当前快里面的线程数量) + threadIdx.x(当前线程在块中的ID)
  • 另一个不同的是 while 部分每次增加现有的线程数量(因为你没有启动到N),知道达到N。这就如同你有一个卡,一次最多只能启动100个块,每个块里有7个线程,也就是一次最多启动700个线程。但N的规模是8000,远远超过700,因此通过while循环可以实现第一次处理[0,699],第二次处理[700,1400],第三次处理[1400,2100]…直到这8000个元素全都被处理完
  • 因此,计算那每一个线程的总ID,可以通过如下数学表达式:
tid = hreadIdx.x + blockIdx.x * blockDim.x 
  • main函数掉调用如下:
int main(void) {//Defining host arraysint h_a[N], h_b[N], h_c[N];//Defining device pointersint *d_a, *d_b, *d_c;// allocate the memorycudaMalloc((void**)&d_a, N * sizeof(int));cudaMalloc((void**)&d_b, N * sizeof(int));cudaMalloc((void**)&d_c, N * sizeof(int));//Initializing Arraysfor (int i = 0; i < N; i++) {h_a[i] = 2 * i*i;h_b[i] = i;}// Copy input arrays from host to device memorycudaMemcpy(d_a, h_a, N * sizeof(int), cudaMemcpyHostToDevice);cudaMemcpy(d_b, h_b, N * sizeof(int), cudaMemcpyHostToDevice);//Calling kernels with N blocks and one thread per block, passing device pointers as parametersgpuAdd << <512, 512 >> >(d_a, d_b, d_c);//Copy result back to host memory from device memorycudaMemcpy(h_c, d_c, N * sizeof(int), cudaMemcpyDeviceToHost);cudaDeviceSynchronize();int Correct = 1;printf("Vector addition on GPU \n");//Printing result on consolefor (int i = 0; i < N; i++) {if ((h_a[i] + h_b[i] != h_c[i])){Correct = 0;}}if (Correct == 1){printf("GPU has computed Sum Correctly\n");}else{printf("There is an Error in GPU Computation\n");}//Free up memorycudaFree(d_a);cudaFree(d_b);cudaFree(d_c);return 0;
}

在这里插入图片描述

1.2 矩阵加法

  • 下边再给两个例子做比较吧

    • 通过一个块中的多个线程计算矩阵相加
    • 通过多个块的多个线程计算矩阵相加
    1. 启动一个块中的多个线程
__global__ void MatAdd(float A[N][N], float B[N][N], float C[N][N]) {//Getting block index of current kernelint i = threadIdx.x;int j = threadIdx.y;C[i][j] = A[i][j] + B[i][j];
}int main()
{   int numblocks = 1;dim3 threadsPerblock(N, N);MatAdd << <numblocks, threadsPerblock >> > (d_a, d_b, d_c);...
}
    1. 启动多个块中的多个线程
__global__ void MatAdd(float A[N][N], float B[N][N], float C[N][N]) {//Getting block index of current kernelint i = blockIdx.x * blockDim.x + threadIdx.x;int j = blockIdx.y * blockDim.y + threadIdx.y;if(i<N&&j<N)C[i][j] = A[i][j] + B[i][j];
}int main()
{//...//核函数定义dim3 threadsPerBlock(16, 16);dim3 numsBlocks(N / threadsPerBlock.x, N / threadsPerBlock.y);MatAdd << < numsBlocks, threadsPerBlock >> > (A, B, C);
}

2. 存储器架构

  • 在GPU上的代码执行被划分为流多处理器、块和线程。
  • GPU有几个不同的存储器空间,每个存储器空间都有特定的特征和用途以及不同的速度和范围。这个存储空间按层次结构被分为不同的组块,比如全局内存、共享内存、本地内存、常量内存和纹理内存,每个组块都可以从程序中的不同点访问。
  • 存储器架构如图:
    在这里插入图片描述
  • 如图所示,每个线程都有自己的本地存储器和寄存器堆。与处理器不同的是,GPU核心有很多寄存器来存储本地数据
  • 当线程使用的数据不适合存储在寄存器堆中或者寄存器堆中装不下的时候,将会使用本地内存。
  • 寄存器堆和本地内存对每个线程都是唯一的,寄存器堆时最快的一种存储器
  • 同一块中的线程具有可有该块中所有线程访问的共享内存。全局内存可被所有的线程访问,它具有相当大的访问延迟,但存在缓存这种东西来给他提速
  • GPU有以及和二级缓存(即L1缓存和L2缓存)。常量内存则是用于存储常量和内核参数之类的只读数据
  • 纹理内存可以利用各种2D和3D的访问模式
  • 所有存储区的特征总结如下:
    在这里插入图片描述

2.1 全局内存

  • 所有的块都可以对全局内存进行读写,该存储器较慢,但是可以从你的代码的任何地方进行读写
  • 缓存可以加速对全局内存的访问
  • 所有通过 cudaMalloc 分配的存储器都是全局内存
  • 来个例子吧
#include <stdio.h>
#define N 5__global__ void gpu_global_memory(int *d_a)
{// "array" is a pointer into global memory on the deviced_a[threadIdx.x] = threadIdx.x;
}int main(int argc, char **argv)
{// Define Host Arrayint h_a[N];//Define device pointer	int *d_a;       cudaMalloc((void **)&d_a, sizeof(int) *N);// now copy data from host memory to device memory cudaMemcpy((void *)d_a, (void *)h_a, sizeof(int) *N, cudaMemcpyHostToDevice);// launch the kernel gpu_global_memory << <1, N >> >(d_a);  // copy the modified array back to the host memorycudaMemcpy((void *)h_a, (void *)d_a, sizeof(int) *N, cudaMemcpyDeviceToHost);printf("Array in Global Memory is: \n");//Printing result on consolefor (int i = 0; i < N; i++) {printf("At Index: %d --> %d \n", i, h_a[i]);}return 0;
}

2.2 本地内存和寄存器堆

  • 本地内存和寄存器堆对每个线程都是唯一的,寄存器时每个线程可用的最快存储器
  • 当内核中使用的变量在寄存器堆中装不下的时候,将会使用本地内存存储它们,这叫寄存器溢出
  • 本地内存使用有两种情况:一种是寄存器不够了,一种是某些情况根本不能放在寄存器中,例如堆一个局部数组的下标进行不定索引的时候
  • 相比寄存器堆,本地内存要慢很多,虽然本地内存通过L1、L2缓存进行了缓冲,但寄存器溢出可能会影响你程序的性能
  • 举个例子:
#include <stdio.h>
#define N 5__global__ void gpu_local_memory(int d_in)
{int t_local;t_local = d_in * threadIdx.x;printf("Value of Local variable in current thread is: %d \n", t_local);
}int main(int argc, char** argv)
{printf("Use of Local Memory on GPU:\n");gpu_local_memory << <1, N >> > (5);cudaDeviceSynchronize();return 0;
}
  • 代码中的 t_local变量是每个线程中局部唯一的,将被存储在寄存器堆中。用这种变量计算的时候,计算速度将是最快速的
    在这里插入图片描述

2.3 高速缓冲存储器

  • 在较新的GPU上,每个流多处理器都含有自己独立的L1缓存,以及GPU有L2缓存,L2缓存是被所有的GPU中的流多处理器都共有的,所有的全局内存访问和本地内存访问都使用这些内存,因为L1缓存在流多处理器内部独有,接近下称执行所需要的硬件单位,所以它的速度非常快
  • 一般来说,L1缓存和共享内存公用同样的存储硬件,一共是64KB,可以配置它们所占内存的比例
  • 所有的全局内存通过L2缓存进行,纹理内存和常量内存也分别有它们独立的缓存

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

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

相关文章

@JsonFormat注解出现日期序列化以及反序列化问题(日期比实际日期少一天)

文章目录 前言一、场景如下所示二、问题分析三、JsonFormat注解是什么以下是 JsonFormat 注解的一些常用属性&#xff1a; 四、解决问题解决方式&#xff1a;只需要指定对应的时区就好效果如下&#xff1a; 五、JsonFormat 注解时出现日期问题总结 前言 在一次的偶然机会下发现…

Python基于PyQt6制作GUI界面——按钮

示例对应的制作的 ui文件 界面如下所示。 <?xml version"1.0" encoding"UTF-8"?> <ui version"4.0"><class>Form</class><widget class"QWidget" name"Form"><property name"geom…

ColossalAI Open-Sora 1.1 项目技术报告 (视频生成)

项目信息 项目地址&#xff1a;https://github.com/hpcaitech/Open-Sora技术报告&#xff1a; Open-Sora 1&#xff1a;https://github.com/hpcaitech/Open-Sora/blob/main/docs/report_01.mdOpen-Sora 1.1&#xff1a;https://github.com/hpcaitech/Open-Sora/blob/main/docs…

swift中json和字典Dict或者数组相互转换,JSONSerialization的强大使用

在Swift中&#xff0c;你可以使用JSONSerialization类将JSON字符串转换为字典。要将 Swift 字典转换为 JSON 字符串&#xff0c;我们可以使用JSONSerialization类的data(withJSONObject:options:)方法。这个方法将字典转换为二进制数据&#xff0c;然后我们可以使用String(data…

Day23:Leetcode:530.二叉搜索树的最小绝对差 + 501.二叉搜索树中的众数 + 236. 二叉树的最近公共祖先

LeetCode&#xff1a;530.二叉搜索树的最小绝对差 问题描述 解决方案&#xff1a; 1.思路 中序遍历 2.代码实现 class Solution {int pre;int ans;public int getMinimumDifference(TreeNode root) {ans Integer.MAX_VALUE;pre -1;dfs(root);return ans;}public void d…

Unity射击游戏开发教程:(26)创建绕圈跑的效果

unity游戏 在本文中,我将介绍如何为敌人创建圆周运动。gif 中显示的确切行为是敌人沿着屏幕向下移动,直到到达某个点,一旦到达该点,它就会绕圈移动。

从浮点数定义到FP8: AI模型中不同的数据类型

背景&#xff1a;AI模型中不同的数据类型对硬件算力和内存的需求是不同的&#xff0c;为了提高模型在硬件平台的吞吐量&#xff0c;减少数据通信带宽需求&#xff0c;往往倾向于将高位宽数据运算转向较低位宽的数据运算。本文通过重新回顾计算机中整数和浮点数的定义&#xff0…

HCIP-Datacom-ARST自选题库__ISIS简答【3道题】

1.IS-1S是链路状态路由协议&#xff0c;便用SPF算法进行路由计算。某园区同时部署了IPv4和IPV6井运行IS-IS实现网络的互联互通&#xff0c;如图所示&#xff0c;该网络IPv4和IPV6开销相同&#xff0c;R1和R4只支持IPV4。缺省情况下&#xff0c;计算形成的IPv6最短路径树中&…

python数据分析——字符串和文本数据2

参考资料&#xff1a;活用pandas库 1、字符串格式化 &#xff08;1&#xff09;格式化字符串 要格式化字符串&#xff0c;需要编写一个带有特殊占位符的字符串&#xff0c;并在字符串上调用format方法向占位符插入值。 # 案例1 varflesh wound s"Its just a {}" p…

solidworks画螺母学习笔记

螺母 单位mm 六边形 直径16mm&#xff0c;水平约束&#xff0c;内圆直径10mm 拉伸 选择两侧对称&#xff0c;厚度7mm 拉伸切除 画相切圆 切除深度7mm&#xff0c;反向切除 拔模角度45 镜像切除 倒角 直径1mm 异形孔向导 螺纹线 偏移打勾&#xff0c;距离为2mm…

java:static关键字用法

在静态方法中不能访问类的非静态成员变量和非静态方法&#xff0c; 因为非静态成员变量和非静态方法都必须依赖于具体的对象才能被调用。 从上面代码里看出&#xff1a; 1.静态方法不能调用非静态成员变量。静态方法test2()中调用非静态成员变量address&#xff0c;编译失败…

从容应对亿级QPS访问,Redis还缺少什么?no.29

众所周知&#xff0c;Redis 在线上实际运行时&#xff0c;面对海量数据、高并发访问&#xff0c;会遇到不少问题&#xff0c;需要进行针对性扩展及优化。本课时&#xff0c;我会结合微博在使用 Redis 中遇到的问题&#xff0c;来分析如何在生产环境下对 Redis 进行扩展改造&…

算法金 | Dask,一个超强的 python 库

本文来源公众号“算法金”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;Dask&#xff0c;一个超强的 python 库 1 Dask 概览 在数据科学和大数据处理的领域&#xff0c;高效处理海量数据一直是一项挑战。 为了应对这一挑战&am…

滑动菜单栏

效果如下&#xff1a; NavigationView 新建menu布局,表示菜单栏的选项 <menu xmlns:android"http://schemas.android.com/apk/res/android"> <group android:checkableBehavior"single"> <item android:id"id/navCall" android…

海外CDN加速方式

随着全球化经济的进一步推进和互联网时代的到来&#xff0c;给对外贸易行业带来了巨大的商机&#xff0c;众多传统的贸易公司都纷纷建立起自已的外贸网站或服务站点等各种信息化平台&#xff0c; 相当多的贸易公司也从他们所构建的平台中得到了很高的利益&#xff0c;然而由于当…

医疗科技:UWB模块为智能医疗设备带来的变革

随着医疗科技的不断发展和人们健康意识的提高&#xff0c;智能医疗设备的应用越来越广泛。超宽带&#xff08;UWB&#xff09;技术作为一种新兴的定位技术&#xff0c;正在引领着智能医疗设备的变革。UWB模块作为UWB技术的核心组成部分&#xff0c;在智能医疗设备中发挥着越来越…

抖音运营_打造高流量的抖音账号

目录 一 账号定位 行业定位 用户定位 内容定位 二 账号人设 我是谁? 我的优势 我的差异化 三 创建账号 名字 头像 简介 四 抖音养号 为什么要养号&#xff1f; 抖音快速养号 正确注册抖音账号 一机一卡一号 实名认证 正确填写账号信息 养号期间的操作 五…

韵搜坊 -- Elastic Stack快速入门

文章目录 现有问题Elastic Stack介绍&#xff08;一套技术栈&#xff09;安装ES安装KibanaElasticsearch概念倒排索引Mapping分词器IK分词器&#xff08;ES插件&#xff09;打分机制 ES的几种调用方式restful api调用&#xff08;http 请求&#xff09;kibana devtools客户端调…

程序员做推广?我劝你别干

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 这是卢松松会员专区&#xff0c;一位会员朋友的咨询&#xff0c;如果你也有自研产品&#xff0c;但不知道如何推广&#xff0c;一定要阅读本文!强烈建议收藏关注&#xff0c;因为你关注的人&#xff0c;决定你看到的…

【机器学习300问】98、卷积神经网络中的卷积核到底有什么用?以边缘检测为例说明其意义。

卷积核是用于从输入数据中提取特征的关键工具。卷积核的设计直接关系到网络能够识别和学习的特征类型。本文让我以边缘检测为例&#xff0c;带大家深入理解卷积核的作用。 一、卷积核的作用 卷积核&#xff0c;又称为过滤器&#xff0c;本质上是一个小的矩阵&#xff0c;其元素…