当前位置: 首页 > news >正文

【CUDA 】第3章 CUDA执行模型——3.5循环展开(1)

CUDA C编程笔记

  • 第三章 CUDA执行模型
    • 3.5 循环展开
      • 3.5.1 展开的规约

待解决的问题:

第三章 CUDA执行模型

3.5 循环展开

循环展开是一种循环优化的技术,通过减少分支出现频率+循环维护指令。
循环主体代码被多次编写,任何封闭的循环可以把迭代次数减少或完全删除。

循环展开因子:循环体的复制数量
迭代次数=原始迭代次数/循环展开因子
在顺序数组中,如果循环迭代的次数在执行前已经知道,循环展开是最有效提升性能的方法。

举一个简单的例子:

for(int i = 0; i < 100; i++){
	a[i] = b[i] + c[i];
}

for(int i = 0; i < 100; i += 2){
	a[i] = b[i] + c[i];
	a[i+1] = b[i+1] + c[i+1];
}

下面的循环迭代次数是上面的一半,这是因为重复操作了一次循环体。
这种提升是由于编译器执行循环展开时低级指令的改进和优化。

目标:通过减少指令消耗和增加更多的独立调度指令来提高性能,更多的并发操作被添加到流水线上,产生更高的指令和内存带宽,帮线程束调度器提供更多符合条件的线程束,隐藏指令或内存延迟。

3.5.1 展开的规约

用一个线程块手动处理两个数据块,每个线程作用域多个数据块,并处理每个块的一个元素

//展开规约1:手动展开两个块block的处理
__global__ void reduceUnrolling2(int *g_idata, int *g_odata, unsigned int n){
    //设置线程id
    unsigned int tid = threadIdx.x;
    unsigned int idx = blockIdx.x * blockDim.x * 2 + threadIdx.x;//这里乘2

    //把全局数据指针转换为块内局部指针
    int *idata = g_idata + blockIdx.x * blockDim.x * 2;//这里乘2

    //展开两个数据块
    if(idx + blockDim.x < n) g_idata[idx] += g_idata[idx + blockDim.x];//每个线程都加一个相邻块的元素,可以算一个迭代,该循环可在数据块间规约
    __syncthreads();

    //在全局内存中就地规约
    for(int stride = blockDim.x/2; stride > 0; stride >>= 1){
        if(tid < stride){
            idata[tid] += idata[tid + stride];
        }

        __syncthreads();
    }

    //把这个块的结果写回全局内容
    if(tid == 0) g_odata[blockIdx.x] = idata[0];
}

可以利用线程块的空闲资源,原本一个线程块中,只有前半个线程块在工作,后半个线程块闲置(交错规约)。把两个数据块一起处理后,后半个线程块也能利用起来

关键点:
①索引的计算

unsigned int idx = blockIdx.x * blockDim.x * 2 + threadIdx.x;
int *idata = g_idata + blockIdx.x * blockDim.x * 2;

在这里插入图片描述

②数据规约

if (idx + blockDim.x < n) 
    g_idata[idx] += g_idata[idx + blockDim.x];

第一个块的内容与第二个块相加,数据存回第一个块。

③块内交错规约

for (int stride = blockDim.x/2; stride > 0; stride >>= 1) {
    if (tid < stride) {
        idata[tid] += idata[tid + stride];
    }
    __syncthreads();
}

完成数据块内的规约,交错规约。

对应的main函数代码也需要修改:网格大小减半,在核函数调用参数、数据传回主机和最后求和方面,都需要除2

//kernel4:reduceUnrolling2 展开规约2:一个线程块处理两个数据块
    cudaMemcpy(d_idata, h_idata, bytes, cudaMemcpyHostToDevice);
    cudaDeviceSynchronize();
    iStart = seconds();
    //因为现在每个线程块处理两个数据块,需要调整内核的执行配置,网格大小减为原来的一半
    reduceUnrolling2<<<grid.x/2, block>>>(d_idata, d_odata, size);//这里写错成了bytes
    cudaDeviceSynchronize();
    iElaps = seconds() - iStart;
    cudaMemcpy(h_odata, d_odata, grid.x/2*sizeof(int), cudaMemcpyDeviceToHost);//需要除2
    gpu_sum = 0;
    for(int i = 0; i<grid.x/2; i++){//需要除2
        gpu_sum += h_odata[i];
    }
    printf("gpu reduceUnrolling2    elapsed %f ms gpu_sum: %d <<<grid %d block %d>>>\n", iElaps, gpu_sum, grid.x/2, block.x);//需要除2

执行结果:

./3-3reduceInteger2 starting reduction atdevice 0: NVIDIA GeForce RTX 3090 
   with array size 16777216 grid 32768 block 512
cpu reduce         elapsed 0.039772 ms cpu_sum: 2139353471
gpu Warmup          elapsed 0.003663 ms gpu_sum: 0 <<<grid 32768 block 512>>>
gpu Neighbored          elapsed 0.001532 ms gpu_sum: 2139353471 <<<grid 32768 block 512>>>
gpu NeighboredLess      elapsed 0.001402 ms gpu_sum: 2139353471 <<<grid 32768 block 512>>>
gpu reduceInterleaved    elapsed 0.001371 ms gpu_sum: 2139353471 <<<grid 32768 block 512>>>
gpu reduceUnrolling2    elapsed 0.001357 ms gpu_sum: 2139353471 <<<grid 16384 block 512>>>

原始的交错规约时间reduceInterleaved0.001371 ms,二合一的交错规约reduceUnrolling20.001357 ms。

原因:一个线程中有更多的独立内存加载/存储操作性能更好,因为内存延迟可以被更好隐藏,可以查看设备内存吞吐量指标,内存吞吐量提高了很多。
在这里插入图片描述
在这里插入图片描述

相关文章:

  • 探讨HMI(人机界面)设计原则,如何通过优秀的设计提升操作效率和用户体验
  • 第二十六:Map的基本原理
  • VM虚拟机安装及Ubuntu安装配置
  • Nacos操作指南
  • I/O进程(全)
  • RGBD惯性SLAM
  • 电弧光的危害有哪些?我们该如何应对?
  • 分布式热点网络
  • 汽车零部件产线节能提效,工业网关解锁数据采集 “密码”
  • 音乐产业新玩法:NFTs如何颠覆传统与挑战未来?
  • JAVA身份证件图像识别(100%可以用)
  • Python 实现的运筹优化系统数学建模详解(多目标规划模型)
  • 报错:Nlopt
  • DICOM通讯(ACSE->DIMSE->Worklist)
  • STM32电机库 电机控制特性
  • 计算机网络(第四章)
  • TiDB 部署指南(单机模式) CentOS 7 安装 MariaDB 教程
  • 弱口令爆破
  • 亚马逊发货系统突发限制,卖家如何破局?
  • 转发表和路由表的差别
  • 神二十成功对接空间站
  • 陕西全省公开征集涉企行政执法问题线索,切实减轻企业负担
  • 广东省发展改革委原副主任、省能源局原局长吴道闻被开除公职
  • 2024年上海发生科技融资997起,位于全国第一
  • 宁德时代与广汽等五车企发布10款巧克力换电新车型:年内将将完成30城1000站计划
  • 土耳其发生6.2级地震,震源深度10千米