CUDA入门(三) 初探线程与块

在配置GPU时一般都看重其的架构,流处理器数,以及显存数。
以英伟达的GPU为例架构一般以科学家的名字来命名,如Fermi(费米),Kepler(开普勒),现在主流的Maxwell(麦克斯韦),Pascal(帕斯卡),不同的架构主要体现在如纹理单元,流处理器,带宽等较为底层的东西不同,为线程与块中主要关心的是其流多处理器(streaming multiprocessor,SM)以及一个流多处理器包含的多个流处理器(scalar processor,SP) 或称为CUDA核(CUDA core)。当控制器将一个线程分配给一个流多处理器后,流多处理器的核协调工作,并行处理所有的线程。
在并行运算时,会出现网格(grid),线程块(block),线程(thread)三个常见的概念。一个网格下包含一个或多个线程块,一个线程块下包含多个线程。
在使用时 block和grid都可以用三维的向量表示,其中block向量的元素是thread,grid的元素是block。当然线程块内的线程数不是无限的,如先前的G80一个线程块内线程数最多为512个,Fermi架构下的线程块的线程数增加到1024个,Kepler架构下的线程数达到了2048个。


如图中grid block,可用dim3来表示三维的向量:

dim3 `gridSize(3×2×1);`
dim3 blockSize(2,2,2);
kernel<<<gridSize,blockSize>>>();

如需二维,则定义时将第三元缺省,如果需一维,则可以直接用整型量int来表示线程和块数;
在核函数kernel中线程是并行执行的,而当一个线程中需要用到另一个线程的量时就需要用到线程的索引:
grid内每个block的位置可以通过blockIdx变量来获得,block的大小可以由变量blockDim来获得,thread在block中位置可以由threadId来获得;

下面再来看先前的两个数组相加的程序,这次我们加上线程索引和块索引;并且将数组的大小扩大到1000:


#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include<stdlib.h>
#include <stdio.h>

cudaError_t addWithCuda(int *c,  int *a,  int *b, unsigned int size);

__global__ void addKernel(int *c,  int *a,  int *b)
{
    int i = threadIdx.x+blockIdx.x*blockDim.x;
    c[i] = a[i] + b[i];
}

int main()
{
     const int arraySize = 1000;
    int a[arraySize];
    int b[arraySize];
    int c[arraySize] = { 0 };

    for (int i = 0; i < arraySize; i++)
    {
        a[i] = rand() % 100;
        b[i] = rand() % 100;

    }
    // Add vectors in parallel.
    cudaError_t cudaStatus = addWithCuda(c, a, b, arraySize);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "addWithCuda failed!");
        return 1;
    }
    for (int i = 0; i < 1000; i++)
    {
        printf("c[%d]=%d\n", i, c[i]);
    }

    getchar();
    // 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 *b, unsigned int size)
{
    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;
    }

    // Allocate GPU buffers for three vectors (two input, one output)    .
    cudaStatus = cudaMalloc((void**)&dev_c, size * sizeof(int));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }

    cudaStatus = cudaMalloc((void**)&dev_a, size * sizeof(int));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }

    cudaStatus = cudaMalloc((void**)&dev_b, size * sizeof(int));
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error;
    }

    // Copy input vectors from host memory to GPU buffers.
    cudaStatus = cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

    cudaStatus = cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

    // Launch a kernel on the GPU with one thread for each element.
    addKernel<<<4,256>>>(dev_c, dev_a, dev_b);

    // Check for any errors launching the kernel
    cudaStatus = cudaGetLastError();
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "addKernel launch failed: %s\n", cudaGetErrorString(cudaStatus));
        goto Error;
    }

    // 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, size * sizeof(int), cudaMemcpyDeviceToHost);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMemcpy failed!");
        goto Error;
    }

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

    return cudaStatus;
}

这里单个块内的线程数为256,块的个数为4。
块内线程数的选择有一个warp的概念,一个block内的线程同时也被打包成多个warp,一个warp由32各线程组成,所以为了让资源不浪费,即warp内的线程数被充分利用,线程数经常设置为32的整数倍。

时间: 2024-10-02 05:30:50

CUDA入门(三) 初探线程与块的相关文章

CUDA入门(四)Visual Profiler

Visual Profiler 在CUDA程序开发的过程中是一个极有利的工具.可以帮助你找到程序中性能不足之处. 下面是<CUDA_Profiler_Users_Guide>对Visual Profiler描述 Visual Profiler 是是一个图形化的剖析工具,可以显示你的应用程序中CPU和GPU的活动情况,利用分析引擎帮助你寻找优化的机会. 其实除了可视化的界面,NVIDIA提供了命令行方式的剖析命令:nvprof.对于初学者,使用图形化的方式比较容易上手,所以本节使用Visual

CUDA入门(二)cuda编程的基本知识与第一个cuda程序

多首先,先来了解一下GPU与CPU的区别,如图 可以看到CPU(Central Processing Unit,中央处理单元),由Control(控制台),ALU(Arithmetic Logic Unit,逻辑计算单元),Cache(高速缓存),而GPU(Graphic Processing Unit,图形处理单元)也是由相同的部件组成,但GPU的计算单元远比CPU多,这就决定了GPU适合大量简单,精度要求低的计算,CPU则适合复杂的,精度要求高的计算.(如果还不了解,可以回学校恶补一下微机原

Python入门(三)——list和tuple,条件判断和循环,dict和set

Python入门(三)--list和tuple,条件判断和循环,dict和set 好的,我们继续来学习python,这次我们讲容器,也就是list,我们可以这样表示 一.list student = ["zhangsan", "lisi", "wangwu"] print student print "长度为:",len(student) 这样我们可以看下输出的内容 我们可以看到打印的内容,以及他的长度是3,也就是有三个子集,

Windows 8风格应用开发入门 三十五 触控输入

Windows 8设备通常具有多点触摸屏,用户可以同时使用多个手指来进行不同的输入交互,如点击. 拖动或收缩等手势操作.另外Windows 8中将触摸.鼠标和笔/触笔交互是作为指针输入进行接收.处理 和管理. 一.手势处理 首先我们来汇总一下Windows 8中常用的手势都有哪些. 开发入门 三十五 触控输入-windows10触控板手势"> 1,点击:用一个手指触摸屏幕,然后抬起手指. 2,长按:用一个手指触摸屏幕并保持不动 . 3,滑动:用一个或多个手指触摸屏幕并向着同一方向移动. 4

Windows 8风格应用开发入门 三十一 构建磁贴

磁贴是吸引用户经常使用应用重要手段之一.我们可将应用程序内较好的内容使用磁贴进行展示. 另外应用程序磁贴是应用程序中的核心部分,而且很可能也是用户最常见到的部分,因此利用 动态磁贴来吸引用户经常使用我们的应用程序! 开发入门 三十一 构建磁贴-磁贴风格"> 本篇博文主要介绍如何创建基本磁贴(也就是默认磁贴)以及如何使用本地通知更新磁贴. 一.创建基本磁贴 基本磁贴也可以叫做默认磁贴.通常我们点击基本磁贴来启动或者切换应用 . 我们可以在应用程序清单文件中设置默认的静态磁贴,并且该静态磁贴分

Windows 8风格应用开发入门 三十 应用生命周期管理

开发入门 三十 应用生命周期管理-风格型产品生命周期"> Windows 8 中可以启动多个应用并在其中切换,我们没有必要担心降低系统速 度或消耗电池电量. 因为系统会自动挂起(有时会终止)在后台正在运行的应用.设计良好的应用可 以由系统挂起.终止以及重新启动,并且这些过程看起来该应用一直在运行中. 一.原理 1.当激活了应用时,无论任何原因,系统都会发送 Activated 事件 2.每当用户切换到桌面 或其他应用时,系统都会挂起你的应用,系统会发送Suspending事件 3.每当用户

Windows 8风格应用开发入门 三 打包发布应用

如何打包Windows 8风格应用程序呢? 首先我们需要使用Windows 8内置Administrator账户才能进行打包,若使用本地普通账户或者Hotmail账号是无法正常打包的. 那么我们如何启用内置的Administrator账户呢?步骤如下: 1) 打开控制面板: 2) 打开管理工具: 3) 打开计算机管理: 4) 在系统工具中打开"本地用户和组": 5) 打开用户文件夹: 6) 选择Administrator: 7) 在Administrator属性中"账户已禁用

DevExpress XtraReports 入门三 创建 Master-Detail(主/从) 报表

原文:DevExpress XtraReports 入门三 创建 Master-Detail(主/从) 报表 本文只是为了帮助初次接触或是需要DevExpress XtraReports报表的人群使用的,为了帮助更多的人不会像我这样浪费时间才写的这篇文章,高手不想的看请路过 本文内容来DevExpress XtraReports帮助文档,如看过类似的请略过. 废话少说 开始正事 一.添加从表  启动 MS Visual Studio (2005.2008.或 2010). 打开有数据感知报表的

AppleWatch开发入门三——代码交互与控制器生命周期

AppleWatch开发入门三--代码交互与控制器生命周期 一.引言         在前两篇博客中,讨论了关于watch开发中框架与界面布局相关,然而主要的逻辑,终究还是要通过代码来实现的,在我们创建了项目之后,就会生成InterfaceController这个文件,它就是我们storyBoard中的入口视图控制器. 二.代码交互与控制器声明周期         storyBoard中的控件我们可以通过拖拽的方式关联到文件中,Action和Outlet两种关联方式基本可以达到我们修改控件和处理