Cuda-By-Example - 4

news2024/11/29 15:43:10

引入Thread概念

第4章使用GPU做并行运算的例子,归结起来就是定义一个内核函数,将数组dev_a和dev_b某一对元素相加。GPU发起N个block运行内核函数。每个block有自己的索引,这样kernel就可以凭借这个索引区分自身,来计算数组对应的元素。在这些例子中,每个block只创建了一个线程。

本章开始引入Thread概念,实际上一个block可以创建多个Thread。为了方便说明,前面将两个数组对应元素相加的运算,之后开始用矢量相加来指称。数组a为矢量a的各个分量,数组b为矢量b的各个分量。之前<<<N, 1>>> 的第二个参数一直没有做介绍,现在我们可以猜到,它就是用来指定一个block里创建多少个线程。

既然这样,我们试试创建一个block,block里创建N个线程来完成上一章的矢量相加的例子。main函数中,调用add的方式从

add<<<N, 1>>>(dev_a, dev_b, dev_c);

改为

add<<<1, N>>>(dev_a, dev_b, dev_c);

内核函数add也需要做个很小的修改。之前各个block通过blockIdx.x作为索引,对矢量a,b的分量求和,现在使用同一个block的各个线程来计算各个分量了,索引肯定不能再用blockIdx了。CUDA贴心的送上threadIdx。

int tid = threadIdx.x;

这个代码就只改了这两个地方。

#include "../common/book.h"

#define N   10

__global__ void add( int *a, int *b, int *c ) {
	// int tid = blockIdx.x;
    int tid = threadIdx.x;
    if (tid < N)
        c[tid] = a[tid] + b[tid];
}

int main( void ) {
    int a[N], b[N], c[N];
    int *dev_a, *dev_b, *dev_c;

    // allocate the memory on the GPU
    HANDLE_ERROR( cudaMalloc( (void**)&dev_a, N * sizeof(int) ) );
    HANDLE_ERROR( cudaMalloc( (void**)&dev_b, N * sizeof(int) ) );
    HANDLE_ERROR( cudaMalloc( (void**)&dev_c, N * sizeof(int) ) );

    // fill the arrays 'a' and 'b' on the CPU
    for (int i=0; i<N; i++) {
        a[i] = i;
        b[i] = i * i;
    }

    // copy the arrays 'a' and 'b' to the GPU
    HANDLE_ERROR( cudaMemcpy( dev_a, a, N * sizeof(int),
                              cudaMemcpyHostToDevice ) );
    HANDLE_ERROR( cudaMemcpy( dev_b, b, N * sizeof(int),
                              cudaMemcpyHostToDevice ) );

    // add<<<N,1>>>( dev_a, dev_b, dev_c );
	add<<<1,N>>>( dev_a, dev_b, dev_c );
	

    // copy the array 'c' back from the GPU to the CPU
    HANDLE_ERROR( cudaMemcpy( c, dev_c, N * sizeof(int),
                              cudaMemcpyDeviceToHost ) );

    // display the results
    for (int i=0; i<N; i++) {
        printf( "%d + %d = %d\n", a[i], b[i], c[i] );
    }

    // free the memory allocated on the GPU
    HANDLE_ERROR( cudaFree( dev_a ) );
    HANDLE_ERROR( cudaFree( dev_b ) );
    HANDLE_ERROR( cudaFree( dev_c ) );

    return 0;
}

Block和Thread的限制

受限于硬件资源,GPU能够支持的block数目和每个block里能包含的thread数目都是有限的。书中的说法是Block的最大数目是65535,每个block最多thread数可以通过设备的属性maxThreadsPerBlock查到。2010年最好GPU,这个数字是512。2024年的今天,这个数字也只翻了一倍1024. 无论如何,我们肯定会遇到元素个数大于最大Block数或者maxThreadsPerBlock,甚至两者的乘积65,535 * 512 = 33,553,920。 

如何突破这一限制?

首先,我们不能再使用<<<N,1>>>和<<<1,N>>>,而要为block数和每Block里thread数选择合适的值。这里不光有上面所说的原因,还牵涉到CUDA的一个机制,属于同一个block的thread可以共享内存。这个内存跟cudaMalloc分配的内存是有区别的,cudaMalloc分配的内存是从GPU里DDR,甚至是供GPU使用的主板DDR。而给block的共享内存要快得多。后面章节会详细使用方法,这里我们先学习block,thread组合在一起用法。

其次,当元素个数大于限制时,分多次处理。

为了更好的说明这个问题,我们先认识一下block和thread的逻辑结构。

图1:Block和Thread布局

以二维为例(比较好画图,实际上最高支持3维的),通过<<<gridDim, blockDim>>>可以在GPU端生成指定数量的线程,线程以上面的方式进行组合管理。

gridDim指定x,y方向线程块的数量,blockDim指定x,y方向thread的数量。当我们只需要一维来处理,那么y方向设置为1,在图1中对应就是蓝色方框显示的部分。我们用一维来处理矢量相加,二维来处理图像。

那么对于图中Block(1,0)里的Thread(1,0),它来计算矢量的哪个分量呢?在本block中,它前面有1个线程,同时前面还有一个block即block(0,0)里的2个线程,所以它应该处理分量的索引应该是 1 +  1*2 = 3。由此对于任意thread,它要处理的分量的所有计算方法是:

int tid = threadIdx.x + blockIdx.x * blockDim.x

这样,我们解决组合使用block和thread时,索引的计算问题。接下来我们来看,当元素个数超出block/thread组合一次性处理能力时该怎么办。

图1中,一次性只能处理6个元素。超过6个元素,比如说当N=15时,thread(1,0)的任务是计算dev_a[3] +  dev_c[3]。最右边的线程thread(2,1)计算dev_a[5] + dev_b[5]。数组再往后,就没有剩余线程可以用了。这时,我们可以让最左边的线程计算完dev_a[0] + dev_b[0]后,来计算dev_a[6]+dev_b[6], 然后再计算dev_a[12] + dev_b[12]。也就是说完成最初计算所得索引的元素的相加之后,跳过 gridDim.x * blockDim.x个元素后, 继续把对应的两个元素相加,直到超出数组大小。

我们的内核函数将修改为:

__global__ void add( int *a, int *b, int *c ) {
    int tid = threadIdx.x + blockIdx.x * blockDim.x;
    while (tid < N) {
        c[tid] = a[tid] + b[tid];
        tid += blockDim.x * gridDim.x;
    }
}

长矢量相加

下面是一个长矢量相加的例子,在随书代码的chapter05\add_loop_long_blocks.cu。分量的个数N 为 33*1024。有了前面的经验,看起来应该没有什么难度。要注意是N比较大,所以CPU这边的a,b,c也改成动态分配了。

#include "../common/book.h"

#define N   (33 * 1024)

__global__ void add( int *a, int *b, int *c ) {
    int tid = threadIdx.x + blockIdx.x * blockDim.x;
    while (tid < N) {
        c[tid] = a[tid] + b[tid];
        tid += blockDim.x * gridDim.x;
    }
}

int main( void ) {
    int *a, *b, *c;
    int *dev_a, *dev_b, *dev_c;

    // allocate the memory on the CPU
    a = (int*)malloc( N * sizeof(int) );
    b = (int*)malloc( N * sizeof(int) );
    c = (int*)malloc( N * sizeof(int) );

    // allocate the memory on the GPU
    HANDLE_ERROR( cudaMalloc( (void**)&dev_a, N * sizeof(int) ) );
    HANDLE_ERROR( cudaMalloc( (void**)&dev_b, N * sizeof(int) ) );
    HANDLE_ERROR( cudaMalloc( (void**)&dev_c, N * sizeof(int) ) );

    // fill the arrays 'a' and 'b' on the CPU
    for (int i=0; i<N; i++) {
        a[i] = i;
        b[i] = 2 * i;
    }

    // copy the arrays 'a' and 'b' to the GPU
    HANDLE_ERROR( cudaMemcpy( dev_a, a, N * sizeof(int),
                              cudaMemcpyHostToDevice ) );
    HANDLE_ERROR( cudaMemcpy( dev_b, b, N * sizeof(int),
                              cudaMemcpyHostToDevice ) );

    add<<<128,128>>>( dev_a, dev_b, dev_c );

    // copy the array 'c' back from the GPU to the CPU
    HANDLE_ERROR( cudaMemcpy( c, dev_c, N * sizeof(int),
                              cudaMemcpyDeviceToHost ) );

    // verify that the GPU did the work we requested
    bool success = true;
    for (int i=0; i<N; i++) {
        if ((a[i] + b[i]) != c[i]) {
            printf( "Error:  %d + %d != %d\n", a[i], b[i], c[i] );
            success = false;
        }
    }
    if (success)    printf( "We did it!\n" );

    // free the memory we allocated on the GPU
    HANDLE_ERROR( cudaFree( dev_a ) );
    HANDLE_ERROR( cudaFree( dev_b ) );
    HANDLE_ERROR( cudaFree( dev_c ) );

    // free the memory we allocated on the CPU
    free( a );
    free( b );
    free( c );

    return 0;
}

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

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

相关文章

Github 优质项目推荐(第七期):涵盖免费服务、API、低代码、安卓root、深度学习

文章目录 Github优质项目推荐 - 第七期一、【LangGPT】&#xff0c;5.7k stars - 让每个人都成为提示专家二、【awesome-selfhosted】&#xff0c;198k stars - 免费软件网络服务和 Web 应用程序列表三、【public-apis】&#xff0c;315k stars - 免费 API四、【JeecgBoot】&am…

如何在 cPanel 中使用 PHP-FPM

PHP性能一直是影响网站托管的一个重要问题。PHP是当前网络上使用最广泛的服务器编程语言&#xff0c;远远领先于其他语言。最受欢迎的内容管理系统和电子商务应用程序&#xff0c;如WordPress、Joomla、Drupal、Magento等&#xff0c;都是用PHP编写的。 PHP-FPM加速了在繁忙服务…

推荐几本编程入门书目

对于编程入门&#xff0c;推荐以下几本书籍&#xff0c;这些书籍覆盖了不同的编程语言&#xff0c;适合零基础的学习者逐步掌握编程基础&#xff1a; 1. 《Python编程快速上手——让繁琐工作自动化》 特点&#xff1a;以简单易懂的方式介绍了Python的基础知识和编程概念&#…

2024年四非边缘鼠鼠计算机保研回忆(记录版 碎碎念)

Hi&#xff0c;大家好&#xff0c;我是半亩花海。写下这篇博客时已然是金秋十月&#xff0c;心中的石头终于落地&#xff0c;恍惚间百感交集。对于保研这条路&#xff0c;我处于摸着石头过河、冲击、随缘的这些状态。计算机保研向来比其他专业难&#xff0c;今年形势更是艰难。…

如何做好薪酬福利体系设计,更好实现员工激励?

如何做好薪酬福利体系设计&#xff0c;更好实现员工激励&#xff1f; 中国作为福利制度建设的佼佼者&#xff0c;尤其在新中国成立后&#xff0c;员工福利体系日益丰富&#xff0c;涵盖了健康保障、休假权益及养老规划等多元化形式&#xff0c;作为间接报酬&#xff0c;有效激…

按模板批量生成工作表

按模板批量生成工作表&#xff0c;前提一个模板&#xff0c;然后用代码遍历循环填写人名&#xff0c;再保存为副本&#xff0c;即可达到效果 按模板批量生成工作表代码 Sub 批量生成员工表()last Sheet73.Range("C65535").End(xlUp).Row 普通区域arr Sheet73…

Redis配置篇 - 指定Redis配置的三种方式,以及Redis配置文件介绍

文章目录 1 指定Redis配置的三种方式1.1 通过命令行参数来指定Redis配置1.2 通过配置文件来指定Redis配置1.3 在服务器运行时更​​改 Redis 配置 2 关于Redis配置文件 1 指定Redis配置的三种方式 1.1 通过命令行参数来指定Redis配置 在redis启动时&#xff0c;可以直接通过命…

UCI-HAR数据集深度剖析:训练仿真与可视化解读

在本篇文章中&#xff0c;我们将深入探讨如何使用Python对UCI人类活动识别&#xff08;HAR&#xff09;数据集进行分割和预处理&#xff0c;以及运用模型网络CNN对数据集进行训练仿真和可视化解读。 一、UCI-HAR数据集分析及介绍 UCI-HAR数据集是一个公开的数据集&#xff0c…

xtu oj 彩球

样例输入# 3 7 5 3 1 2 3 1 2 3 1 6 4 3 1 2 2 3 1 3 6 2 2 1 2 3 4 5 5 样例输出# Yes Yes No 滑动窗口问题 AC代码 #include<stdio.h> #define N 10005 int main(){int T;scanf("%d",&T);while(T--){int n,m,k,i,flag1;//m个k种颜色 scanf("%d%d…

【C++入门篇 - 3】:从C到C++第二篇

文章目录 从C到C第二篇new和delete命名空间命名空间的访问 cin和coutstring的基本使用 从C到C第二篇 new和delete 在C中用来向系统申请堆区的内存空间 New的作用相当于C语言中的malloc Delete的作用相当于C语言中的free 注意&#xff1a;在C语言中&#xff0c;如果内存不够…

一台电脑轻松接入CANFD总线_来可CNA板卡介绍

在工业控制领域&#xff0c;常常使用的总线技术有CAN(FD)、RS-232、RS-485、Modbus、Profibus、Profinet、EtherCAT等。RS-485以其长距离通信能力著称&#xff0c;Modbus广泛应用于PLC等设备&#xff0c;EtherCAT则以其低延迟和高实时性在自动化系统中备受青睐。 其中&#xff…

The Ether: EvilScience VM靶机打靶记录

一、靶机介绍 下载地址&#xff1a;https://www.mediafire.com/file/502nbnbkarsoisb/theEther.zip 二、信息收集 扫描一下目标靶机的ip&#xff1a;192.168.242.135 arp-scan -l 扫描端口 nmap -p- -A 192.168.242.135 这里看开放了 22&#xff0c;80端口 扫描一下目录 …

剧场的客户端形式区别,APP,小程序,H5的不同优势以及推广方案

剧场的客户端形式区别与推广策略 在数字化时代&#xff0c;剧场的线上化成为大势所趋。不同的线上平台如APP、小程序和H5各有千秋&#xff0c;如何选择最适合自己的平台&#xff0c;并制定有效的推广方案&#xff0c;成为了剧场管理者需要考虑的重要问题。 APP&#xff1a;深度…

【每日刷题】Day138

【每日刷题】Day138 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 6. Z 字形变换 - 力扣&#xff08;LeetCode&#xff09; 2. 38. 外观数列 - 力扣&#xff08;Leet…

【2024版】超详细安装教程以及环境配置和使用指南, 学Python看完这一篇就够了!

windows版本PyCharm安装 1.点击蓝色链接---->PyCharm官网&#xff0c;进入之后是这个界面。 2.点击"Download"进入下载页面 注意&#xff1a;若有老版本IDEA&#xff0c;先卸载&#xff08;控制面板卸载&#xff09;&#xff0c;再安装&#xff01; 以管理员身…

1788C - Matching Numbers

给你n&#xff0c;求permutation [ 1 , 2 n ] [1,2n] [1,2n]数字能不能构造出 n n n个数字&#xff0c;每个数字是其中两个数字之和。 偶数不行&#xff0c;通过之和相同构造n和k的关系&#xff0c;k是构造后的首项。 奇数的话&#xff0c;我们把 [ 1 , 2 n ] [1,2n] [1,2n]…

两种方式创建Vue项目

文章目录 引言利用Vue命令创建Vue项目准备工作安装Vue CLI创建Vue项目方法一&#xff1a;使用vue init命令方法二&#xff1a;使用vue create命令启动Vue项目 利用Vite工具创建Vue项目概述利用Vite创建项目启动项目 结语 引言 大家好&#xff0c;今天我将向大家展示如何使用不…

【计算机方向】三本计算机视觉IEEE系列,发文量高,影响因子呈上升趋势,备受国人追捧!

本期将为您带来三本计算机SCI 妥妥毕业神刊&#xff01; IEEE Transactions on Pattern Analysis and Machine Intelligence IEEE Transactions on Knowledge and Data Engineering IEEE Transactions on Cognitive and Developmental Systems 期刊名称&#xff1a;IEEE Tr…

如何在Ubuntu上更改MySQL数据存储路径

文章目录 0 背景1 备份现有数据库数据2 停止 MySQL 服务3 复制现有的 MySQL 数据到新目录4 修改 MySQL 配置文件5 更新 AppArmor 或 SELinux 配置&#xff08;如有启用&#xff09;6. 修改 MySQL 系统文件中的 datadir7. 启动 MySQL 服务8. 验证更改参考资料 0 背景 在原先划分…

Cpp::STL—list类的模拟实现(上)(13)

文章目录 前言一、结点类的实现二、迭代器类的实现迭代器类的存在意义迭代器类的模板参数构造函数运算符的重载--运算符的重载、!运算符的重载*运算符的重载->运算符的重载 总结 前言 注意本篇难度偏高&#xff0c;其主要体现在迭代器类的实现&#xff01;   什么&#xf…