CUDA编程结构、存储管理、线程管理杂谈

CUDA编程结构、存储管理、线程管理杂谈
CUDA编程结构
一个异构环境,通常有多个CPU多个GPU,他们都通过PCIe总线相互通信,也是通过PCIe总线分隔开的。所以要区分一下两种设备的内存:
1)主机:CPU及其内存
2)设备:GPU及其内存
这两个内存从硬件到软件都是隔离的(CUDA6.0 以后支持统一寻址),目前先不研究统一寻址,现在还是用内存来回拷贝的方法来编写调试程序,以巩固大家对两个内存隔离这个事实的理解。
一个完整的CUDA应用可能的执行顺序,如图10-9所示。
图10-9 一个完整的CUDA应用可能的执行顺序
    从host的串行到调用核函数(核函数被调用后控制马上归还主机线程,也就是在第一个并行代码执行时,很有可能第二段host代码已经开始同步执行了)。
接下来的研究层次是:
1)内存
2)线程
3)核函数
①启动核函数
②编写核函数
③验证核函数
4)错误处理
内存管理
内存管理在传统串行程序是非常常见的,寄存器空间,栈空间内的内存由机器自己管理,堆空间由用户控制分配和释放,CUDA程序同样,只是CUDA提供的API可以分配管理设备上的内存,当然也可以用CDUA管理主机上的内存,主机上的传统标准库也能完成主机内存管理。
    一些主机API和CUDA C的API的对比,见表10-1。
表10-1 一些主机API和CUDA C的API的对比

标准C函数

CUDA C 函数

说明

malloc

cudaMalloc

内存分配

memcpy

cudaMemcpy

内存复制

memset

cudaMemset

内存设置

free

cudaFree

释放内存

先研究最关键的一步,这一步要走总线的。
cudaError_t cudaMemcpy(void * dst,const void * src,size_t count,
  cudaMemcpyKind kind)
这个函数是内存拷贝过程,可以完成以下几种过程(cudaMemcpyKind kind)
1)cudaMemcpyHostToHost
2)cudaMemcpyHostToDevice
3)cudaMemcpyDeviceToHost
4)cudaMemcpyDeviceToDevice
这四个过程的方向可以清楚的从字面上看出来,这里就不废话了,如果函数执行成功,则会返回 cudaSuccess 否则返回 cudaErrorMemoryAllocation
使用下面这个指令可以吧上面的错误代码翻译成详细信息:
char* cudaGetErrorString(cudaError_t error)
内存是分层次的,可以简单地描述,但是不够准确,后面会详细介绍每一个具体的环节,如图10-10所示。
图10-10 CUDA内存是分层次的
共享内存(shared Memory)和全局内存(global Memory)后面会特别详细深入的研究,这里来个例子,两个向量的加法:
/*
* 3_sum_arrays
*/
#include <cuda_runtime.h>
#include <stdio.h>
#include "freshman.h"
 
void sumArrays(float * a,float * b,float * res,const int size)
{
  for(int i=0;i<size;i+=4)
  {
    res[i]=a[i]+b[i];
    res[i+1]=a[i+1]+b[i+1];
    res[i+2]=a[i+2]+b[i+2];
    res[i+3]=a[i+3]+b[i+3];
  }
}
__global__ void sumArraysGPU(float*a,float*b,float*res)
{
  int i=threadIdx.x;
  res[i]=a[i]+b[i];
}
int main(int argc,char **argv)
{
  int dev = 0;
  cudaSetDevice(dev);
 
  int nElem=32;
  printf("Vector size:%d\n",nElem);
  int nByte=sizeof(float)*nElem;
  float *a_h=(float*)malloc(nByte);
  float *b_h=(float*)malloc(nByte);
  float *res_h=(float*)malloc(nByte);
  float *res_from_gpu_h=(float*)malloc(nByte);
  memset(res_h,0,nByte);
  memset(res_from_gpu_h,0,nByte);
 
  float *a_d,*b_d,*res_d;
  CHECK(cudaMalloc((float**)&a_d,nByte));
  CHECK(cudaMalloc((float**)&b_d,nByte));
  CHECK(cudaMalloc((float**)&res_d,nByte));
 
  initialData(a_h,nElem);
  initialData(b_h,nElem);
 
  CHECK(cudaMemcpy(a_d,a_h,nByte,cudaMemcpyHostToDevice));
  CHECK(cudaMemcpy(b_d,b_h,nByte,cudaMemcpyHostToDevice));
 
  dim3 block(nElem);
  dim3 grid(nElem/block.x);
  sumArraysGPU<<<grid,block>>>(a_d,b_d,res_d);
  printf("Execution configuration<<<%d,%d>>>\n",block.x,grid.x);
 
  CHECK(cudaMemcpy(res_from_gpu_h,res_d,nByte,cudaMemcpyDeviceToHost));
  sumArrays(a_h,b_h,res_h,nElem);
 
  checkResult(res_h,res_from_gpu_h,nElem);
  cudaFree(a_d);
  cudaFree(b_d);
  cudaFree(res_d);
 
  free(a_h);
  free(b_h);
  free(res_h);
  free(res_from_gpu_h);
 
  return 0;
}
然后使用nvcc编译程序(代码库用cmake管理工程,更方便)。
解释下内存管理部分的代码:
cudaMalloc((float**)&a_d,nByte);
分配设备端的内存空间,为了区分设备和主机端内存,可以给变量加后缀或者前缀h_表示host,d_表示device
一个经常会发生的错误就是混用设备和主机的内存地址!!
线程管理
当内核函数开始执行,如何组织GPU的线程就变成了最主要的问题了,必须明确,一个核函数只能有一个grid,一个grid可以有很多个块,每个块可以有很多的线程,这种分层的组织结构使得并行过程更加自如灵活,如图10-11所示。
图10-11 CUDA线程管理示例
一个线程块block中的线程,可以完成下述协作:
1)同步
2)共享内存
不同块内线程不能相互影响!他们是物理隔离的!
接下来就是给每个线程一个编号了,知道每个线程都执行同样的一段串行代码,那么怎么让这段相同的代码对应不同的数据呢?首先第一步就是让这些线程彼此区分开,才能对应到相应从线程,使得这些线程也能区分自己的数据。如果线程本身没有任何标记,那么没办法确认其行为。
    依靠下面两个内置结构体确定线程标号:
1)blockIdx(线程块在线程网格内的位置索引)
2)threadIdx(线程在线程块内的位置索引)
这里的Idx是index的缩写(之前一直以为是identity x的缩写),这两个内置结构体基于 uint3 定义,包含三个无符号整数的结构,通过三个字段来指定:
1)blockIdx.x
2)blockIdx.y
3)blockIdx.z
4)threadIdx.x
5)threadIdx.y
6)threadIdx.z
上面这两个是坐标,当然要有同样对应的两个结构体来保存其范围,也就是blockIdx中三个字段的范围threadIdx中三个字段的范围:
1)blockDim
2)gridDim
他们是dim3类型(基于uint3定义的数据结构)的变量,也包含三个字段x,y,z.
1)blockDim.x
2)blockDim.y
3)blockDim.z
网格和块的维度一般是二维和三维的,也就是说一个网格通常被分成二维的块,而每个块常被分成三维的线程。
    注意:dim3是手工定义的,主机端可见。uint3是设备端在执行的时候可见的,不可以在核函数运行时修改,初始化完成后uint3值就不变了。他们是有区别的!这一点必须要注意。
下面有一段代码,块的索引和维度:
/*
*1_check_dimension
*/
#include <cuda_runtime.h>
#include <stdio.h>
__global__ void checkIndex(void)
{
  printf("threadIdx:(%d,%d,%d) blockIdx:(%d,%d,%d) blockDim:(%d,%d,%d)\
  gridDim(%d,%d,%d)\n",threadIdx.x,threadIdx.y,threadIdx.z,
  blockIdx.x,blockIdx.y,blockIdx.z,blockDim.x,blockDim.y,blockDim.z,
  gridDim.x,gridDim.y,gridDim.z);
}
int main(int argc,char **argv)
{
  int nElem=6;
  dim3 block(3);
  dim3 grid((nElem+block.x-1)/block.x);
  printf("grid.x %d grid.y %d grid.z %d\n",grid.x,grid.y,grid.z);
  printf("block.x %d block.y %d block.z %d\n",block.x,block.y,block.z);
  checkIndex<<<grid,block>>>();
  cudaDeviceReset();
  return 0;
}
接下来这段代码是检查网格和块的大小的:
/*
*2_grid_block
*/
#include <cuda_runtime.h>
#include <stdio.h>
int main(int argc,char ** argv)
{
  int nElem=1024;
  dim3 block(1024);
  dim3 grid((nElem-1)/block.x+1);
  printf("grid.x %d block.x %d\n",grid.x,block.x);
 
  block.x=512;
  grid.x=(nElem-1)/block.x+1;
  printf("grid.x %d block.x %d\n",grid.x,block.x);
 
  block.x=256;
  grid.x=(nElem-1)/block.x+1;
  printf("grid.x %d block.x %d\n",grid.x,block.x);
 
  block.x=128;
  grid.x=(nElem-1)/block.x+1;
  printf("grid.x %d block.x %d\n",grid.x,block.x);
 
  cudaDeviceReset();
  return 0;
}

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

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

相关文章

Vue3实现excel文件预览和打印

预览excel 关于实现excel文档在线预览的做法,一种方式是通过讲文档里的数据处理成html,一种是将文档处理成图片进行预览。 首先我们先讲一下实现html这种方式预览的。Excel预览用的是xlsx这个库。 xlsx xlsx是一个优秀的表格处理库,是一款适用于浏览器和nodejs的开源电子表格…

计算机电源管理

在计算机电源管理中,S1, S2, S3, S4 代表不同的电源状态或睡眠状态。 了解这些状态,对计算机设备理解功耗及工作状态有很大帮助。最近公司开会,系统同事有讲S3状态功耗很低,我猜和电脑的睡眠、息屏有关。。。emmm,不懂就要学 以下是这些状态的详细说明:S1 状态(低电量等…

使用SRS实现了音视频通话,以及共享桌面的功能

引言在三年前,写智能小车的时候,当时小车上有一个摄像头需要采集,实现推拉流的操作,技术选型当时第一版用的是nginx的rtmp的推拉流,服务器的配置环境是centos,2H4G3M的一个配置,nginx的rtmp的延迟是20秒,超慢,后来研究了SRS以及ZLMediaKit这两个开源的推拉流服务器,没…

算法-动态规划-完全背包

LeetCode算法刷题 动态规划之完全背包0. 动态规划五部曲:确定dp数组(dp table)以及下标的含义 确定递推公式 dp数组如何初始化 确定遍历顺序 举例推导dp数组1. 完全背包问题 完全背包问题中,每个物品都有无数个,可以重复选择。二维dp数组int[][] dp = new int[n][totalWei…

Clion\+OpenCV(C\+\+版)开发环境配置教程Win/Mac

合集 - 环境配置(2)1.最全!嵌入式STM32单片机开发环境配置教学Win/Mac!!!08-282.最简最速!C++版OpenCV安装配置教程Win/Mac!!!08-28收起 Clion+OpenCV(C++版)开发环境配置教程Win/Mac 平时在学习和比赛的时候都是使用的Python版本的OpenCV,最近遇到了一个项目使用的上…

开源活动预告|抖音集团专家聚焦电商、PB级实时场景带来数据技术分享

⌈ Apache Doris 城市行 Meetup ⌋ 第四站北京活动,正在火热报名。8 月 31 日 13:30,ApacheDoris x 字节跳动开源联合 Meetup 北京站即将开启。多位来自抖音集团的数据工程师,将聚焦电商场景、PB级实时场景,带来数据技术实战分享。现场参会名额有限,感兴趣的同学抓紧报名占…

TimeWheel算法介绍及在应用上的探索

作者:来自 vivo 互联网服务器团队- Li Fan本文从追溯时间轮算法的出现,介绍了时间轮算法未出现前,基于队列的定时任务实现,以及基于队列的定时任务实现所存在的缺陷。接着我们介绍了时间轮算法的算法思想及其数据结构,详细阐述了三种时间轮模型的数据结构和优劣性。 再次,…

再推2款底层源码调试工具ILSpy和dotPeek

ILSpy_binaries_8.2.0.7535-x64 https://github.com/icsharpcode/ILSpy/releases 和 JetBrains dotPeek 2024.2.2 https://www.jetbrains.com.cn/decompiler/ 都免费

模拟退火模型 —— 入门案例

简介 模拟退火算法(Simulated Annealing, SA) 是一种概率型全局优化算法,它受到物理退火过程的启发。在固体材料的退火过程中,材料被加热到一定温度后缓慢冷却,其内部结构逐渐趋于稳定,最终达到能量最低的平衡状态。模拟退火算法正是模仿这一过程,用于寻找数学问题中的全…

HarmonyOS SDK实况窗服务

HarmonyOS SDK实况窗服务(Live View Kit)作为一个实时呈现应用服务信息变化的小窗口,遍布于设备的各个使用界面,它的魅力在于将复杂的应用场景信息简洁提炼并实时刷新,在不影响当前其他应用操作的情况下,时刻向用户展示最新的信息动态,用户也可以点击实况窗卡片或胶囊进…

通讯协议

UART通用异步收发器,串行、全双工、异步通信总线。重点是异步,和同步相对应,意思是不需要同步的时钟,通信两端预先约定好波特率(每秒传多少bit),而不是由时钟触发的。波特率:用于描述UART通信时的通信速度,其单位为bps 即每秒钟传送的bit的数量。串口一次发送一个字节…

Oracle同一台服务器创建多个数据库

有时候我们需要再同一台机器上创建多个数据库服务(不是单纯的数据库实例),每一个数据库可以有单独的服务运行,只是在一个机器环境而已。可以在不同的端口上监听,也可以在相同端口监听创建多个数据库步骤 安装完Oracle数据库后,会自动安装很多工具,这里我们使用Database …