CUDA学习:进一步理解块、线程

1. CUDA里的块和线程概念可以用下面的图来表示:

每个grid里包含可以用二维数组表示的block(块),每个block又包含一个可以用二维数组表示的thread(线程)。

2.  二维数组块和线程可以用dim3来定义:

  dim3 blockPerGrid(3,2); //定义了3*2=6个blocks

  dim3 threadsPerBlock(3,3);//定义了3*3=9个threads

3. 运行时每个线程的代码,如何知道自己是在哪个块里的哪个线程中运行呢?通过下面的变量计算:

* 块的二维索引:(blockIdx.x,blockIdx.y), 块二维数组x方向长度 gridDim.x,y方向长度 gridDim.y

* 每个块内线程的二维索引:(threadIdx.x,threadIdx.y) ,线程二维数组x方向长度 blockDim.x,y方向长度 blockDim.y

* 每个grid内有gridDim.x*gridDim.y个块,每个块内有 blockDim.x*blockDim.y个线程

通过上述参数可以确定每个线程的唯一编号:

tid= (blockIdx.y*gridDim.x+blockIdx.x)* blockDim.x*blockDim.y+threadIdx.y*blockDim.x+threadIdx.x;

4.下面具体一个例子,来引用上诉这些变量(仍引用上一个博客的N个数求和例子)

上一篇文章其实是用块和线程都是一维素组,现在我们用二维数组来实现

关键语句:

  dim3 blockPerGrid(BLOCKS_PerGridX,BLOCKS_PerGridY); //定义了块二维数组

  dim3 threadsPerBlock(THREADS_PerBlockX,THREADS_PerBlockY);//定义了线程二维数组

SumArray<<<blockPerGrid, threadsPerBlock>>>(dev_c, dev_a);

完整代码如下:

//////////////////////////////////////////

//////////////////////////////////////////

#include "cuda_runtime.h"
#include "device_launch_parameters.h"

#include <stdio.h>

cudaError_t addWithCuda(int *c, int *a);

#define TOTALN 72120

#define BLOCKS_PerGridX 2
#define BLOCKS_PerGridY 2
#define BLOCKS_PerGrid (BLOCKS_PerGridX*BLOCKS_PerGridY)

#define THREADS_PerBlockX 2 //2^8
#define THREADS_PerBlockY 4 //2^8
#define THREADS_PerBlock (THREADS_PerBlockX*THREADS_PerBlockY)

dim3 blockPerGrid(BLOCKS_PerGridX,BLOCKS_PerGridY); //定义了块二维数组
dim3 threadsPerBlock(THREADS_PerBlockX,THREADS_PerBlockY);//定义了线程二维数组

//grid 中包含BLOCKS_PerGridX*BLOCKS_PerGridY(2*2)个block
// blockIdx.x方向->,最大gridDim.x
// |***|***|*
// |0,0|0,1| blockIdx.y
// |***|***|* 方
// |1,0|1,1| 向
// |--------
// * ↓
// * 最大gridDim.y
// *

//每个block中包括THREADS_PerBlockX*THREADS_PerBlockY(4*2)个线程
// threadIdx.x方向->,最大值blockDim.x
// |***|***|*
// |0,0|0,1|
// |***|***|* threadIdx.y
// |1,0|1,1| 方
// |-------- 向
// |2,0|2,1| ↓
// |-------- 最大blockDim.y
// |3,0|3,1|
// |--------
// /

__global__ void SumArray(int *c, int *a)//, int *b)
{
__shared__ unsigned int mycache[THREADS_PerBlock];//设置每个块内同享内存threadsPerBlock==blockDim.x
//i为线程编号
int tid= (blockIdx.y*gridDim.x+blockIdx.x)* blockDim.x*blockDim.y+threadIdx.y*blockDim.x+threadIdx.x;
int j = gridDim.x*gridDim.y*blockDim.x*blockDim.y;//每个grid里一共有多少个线程
int cacheN;
unsigned sum,k;

cacheN=threadIdx.y*blockDim.x+threadIdx.x; //

sum=0;
while(tid<TOTALN)
{
sum += a[tid];// + b[i];
tid = tid+j;//获取下一个Grid里的同一个线程位置的编号

}

mycache[cacheN]=sum;

__syncthreads();//对线程块进行同步;等待该块里所有线程都计算结束

//下面开始计算本block中每个线程得到的sum(保存在mycache)的和
//递归方法:(参考《GPU高性能编程CUDA实战中文》)
//1:线程对半加:

k=THREADS_PerBlock>>1;
while(k)
{
if(cacheN<k)
{
//线程号小于一半的线程继续运行这里加
mycache[cacheN] += mycache[cacheN+k];//数组序列对半加,得到结果,放到前半部分数组,为下次递归准备
}
__syncthreads();//对线程块进行同步;等待该块里所有线程都计算结束
k=k>>1;//数组序列,继续对半,准备后面的递归
}

//最后一次递归是在该块的线程0中进行,所有把线程0里的结果返回给CPU
if(cacheN==0)
{
c[blockIdx.y*gridDim.x+blockIdx.x]=mycache[0];
}

}

int main()
{

int a[TOTALN] ;
int c[BLOCKS_PerGrid] ;

unsigned int j;
for(j=0;j<TOTALN;j++)
{
//初始化数组,您可以自己填写数据,我这里用1
a[j]=1;
}

// 进行并行求和
cudaError_t cudaStatus = addWithCuda(c, a);

if (cudaStatus != cudaSuccess) {
fprintf(stderr, "addWithCuda failed!");
return 1;
}

unsigned int sum1,sum2;
sum1=0;
for(j=0;j<BLOCKS_PerGrid;j++)
{
sum1 +=c[j];
}
//用CPU验证和是否正确

sum2=0;
for(j=0;j<TOTALN;j++)
{
sum2 += a[j];
}

printf("sum1=%d; sum2=%d\n",sum1,sum2);

// cudaDeviceReset must be called before exiting in order for profiling and
// tracing tools such as Nsight and Visual Profiler to show complete traces.
cudaStatus = cudaDeviceReset();
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaDeviceReset failed!");
return 1;
}

return 0;
}

// Helper function for using CUDA to add vectors in parallel.

cudaError_t addWithCuda(int *c, int *a)
{
int *dev_a = 0;
int *dev_b = 0;
int *dev_c = 0;
cudaError_t cudaStatus;

// Choose which GPU to run on, change this on a multi-GPU system.
cudaStatus = cudaSetDevice(0);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaSetDevice failed! Do you have a CUDA-capable GPU installed?");
goto Error;
}

// 申请一个GPU内存空间,长度和main函数中c数组一样
cudaStatus = cudaMalloc((void**)&dev_c, BLOCKS_PerGrid * sizeof(int));
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaMalloc failed!");
goto Error;
}
// 申请一个GPU内存空间,长度和main函数中a数组一样
cudaStatus = cudaMalloc((void**)&dev_a, TOTALN * sizeof(int));
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaMalloc failed!");
goto Error;
}

//////////////////////////////////////////////////
//////////////////////////////////////////////////
//////////////////////////////////////////////////
//////////////////////////////////////////////////
//////////////////////////////////////////////////
// Copy input vectors from host memory to GPU buffers.
//将a的数据从cpu中复制到GPU中
cudaStatus = cudaMemcpy(dev_a, a, TOTALN * sizeof(int), cudaMemcpyHostToDevice);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaMemcpy failed!");
goto Error;
}

//////////////////////////////////////////////////
//////////////////////////////////////////////////
//////////////////////////////////////////////////
//////////////////////////////////////////////////
//////////////////////////////////////////////////

// dim3 threadsPerBlock(8,8);
//dim3 blockPerGrid(8,8);

// Launch a kernel on the GPU with one thread for each element.
//启动GPU上的每个单元的线程
SumArray<<<blockPerGrid, threadsPerBlock>>>(dev_c, dev_a);//, dev_b);

// cudaDeviceSynchronize waits for the kernel to finish, and returns
// any errors encountered during the launch.
//等待全部线程运行结束
cudaStatus = cudaDeviceSynchronize();
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching addKernel!\n", cudaStatus);
goto Error;
}

// Copy output vector from GPU buffer to host memory.
cudaStatus = cudaMemcpy(c, dev_c, BLOCKS_PerGrid * sizeof(int), cudaMemcpyDeviceToHost);
cudaStatus = cudaMemcpy(a, dev_a, TOTALN * sizeof(int), cudaMemcpyDeviceToHost);
if (cudaStatus != cudaSuccess) {
fprintf(stderr, "cudaMemcpy failed!");
goto Error;
}

Error:
cudaFree(dev_c);
cudaFree(dev_a);

return cudaStatus;
}

时间: 2024-10-11 06:21:23

CUDA学习:进一步理解块、线程的相关文章

css学习笔记----深刻理解块级元素和内联元素

前言 因为之前学习css时候急于求成,学习的很不扎实,对于display还有块级元素等知识都了解的非常模糊,于是,最近我决定来恶补一下. 其实块级元素.内联元素和display有着密不可分的关系,下面先看一下display的三个相关的属性. 一.display display跟块级元素相关的包括以下三个属性  block,inline,inline-block三个属性.(当然还包括很多其他的属性,详情请见 http://www.w3school.com.cn/cssref/pr_class_di

玩深度学习选哪块英伟达 GPU?有性价比排名还不够!

本文來源地址:https://www.leiphone.com/news/201705/uo3MgYrFxgdyTRGR.html 与"传统" AI 算法相比,深度学习(DL)的计算性能要求,可以说完全在另一个量级上. 而 GPU 的选择,会在根本上决定你的深度学习体验.那么,对于一名 DL 开发者,应该怎么选择合适的 GPU 呢?这篇文章将深入讨论这个问题,聊聊有无必要入手英特尔协处理器 Xeon Phi,并将各主流显卡的性能.性价比制成一目了然的对比图,供大家参考. 先来谈谈选择

CUDA学习之二:shared_memory使用,矩阵相乘

CUDA中使用shared_memory可以加速运算,在矩阵乘法中是一个体现. 矩阵C = A * B,正常运算时我们运用 C[i,j] = A[i,:] * B[:,j] 可以计算出结果.但是在CPU上完成这个运算我们需要大量的时间,设A[m,n],B[n,k],那么C矩阵为m*k,总体,我们需要做m*n*k次乘法运算,m*(b-1)*k次加法运算,并且是串行执行,总体的复杂度为O(m*n*k) . 矩阵类: 1 class Matrix 2 { 3 public: 4 int cols; /

CUDA学习5 常量内存与事件

当线程束中的所有线程都访问相同的只读数据时,使用常量内存将获得额外的性能提升. 常量内存大小限制为64k. 以下摘自hackairM的博文CUDA学习--内存处理之常量内存(4). 常量内存其实只是全局内存的一种虚拟地址形式,并没有特殊保留的常量内存块.常量内存有两个特性,一个是高速缓存,另一个是它支持将单个值广播到线程束中的每个线程.但要注意的是,对于那些数据不太集中或者数据重用率不高的内存访问,尽量不要使用常量内存. 当常量内存将数据分配或广播到线程束中的每个线程时(注意,实际上硬件会将单次

Android学习笔记之ExecutorService线程池的应用....

PS:转眼间就开学了...都不知道这个假期到底是怎么过去的.... 学习内容: ExecutorService线程池的应用... 1.如何创建线程池... 2.调用线程池的方法,获取线程执行完毕后的结果... 3.关闭线程...   首先我们先了解一下到底什么是线程池,只有了解了其中的道理,我们才能够进行应用...java.util.concurrent.ExecutorService表述了异步执行的机制   首先我们简单的举一个例子... package executor; import ja

linux学习之进程,线程和程序

                                                                                  程序.进程和线程的概念 1:程序和进程的差别 进程的出现最初是在UNIX下,用于表示多用户,多任务的操作系统环境下,应用程序在内存环境中基本执行单元的概念.进程是UNIX操作系统环境最基本的概念.是系统资源分配的最小单位.UNIX操作系统下的用户管理和资源分配等工作几乎都是操作系统通过对应用程序进程的控制实现的! 当使用c c++ j

[express.js学习笔记]理解Router

(本文内容纯属个人理解,仅供学习探讨) 博主的工作用的是Java Web,私下对JavaScript很感兴趣,也就接触了Node.js,听过Node一般使用Express来搭建Web服务器,就找到了Express,开始阅读文档和例子. 发现官方文档API页面的导航列出了express的几个核心的类(对象): 1. express 2. Application 3. Request 4. Response 5. Router 其中,按照我的理解,express是一个Application对象的工厂

SSH加密原理、RSA非对称加密算法学习与理解

首先声明一下,这里所说的SSH,并不是Java传统的三大框架,而是一种建立在应用层和传输层基础上的安全外壳协议,熟悉Linux的朋友经常使用到一 个SSH Secure Shell Cilent的工具,本文也是基于此工具加密原理的学习,在SSH的加密原理中,使用到了RSA非对称加密算法,本文也一并做了学习和了解. 非对称加密算法 在日常的工作生产中, 我们经常需要进行数据的通讯,开发人员经常需要对数据进行加解密操作,以保证数据的安全.数据的加密算法非为对称加密和非对称加密两种,常用的DES.三

CUDA学习,第一个kernel函数及代码讲解

前一篇CUDA学习,我们已经完成了编程环境的配置,现在我们继续深入去了解CUDA编程.本博文分为三个部分,第一部分给出一个代码示例,第二部分对代码进行讲解,第三部分根据这个例子介绍如何部署和发起一个kernel函数. 一.代码示例 二.代码解说 申明一个函数,用于检测CUDA运行中是否出错. kernel函数,blockIdx.x表示block在x方向的索引号,blockDim.x表示block在x方向的维度,threadIdx.x表示thread在x方向的索引号. 这里也许你会问,为什么在x方