CUDA C编程3 - 并行性衡量指标

2023-11-09

系列文章目录



前言

CUDA编程,就是利用GPU设备的并行计算能力实现程序的高速执行。CUDA内核函数关于网格(Grid)和模块(Block)大小的最优设置才能保证CPU设备的这种并行计算能力得到充分应用。这里介绍并行性衡量指标,可以衡量最优性能的网格和模块大小设置。


一. CUDA C并行性衡量指标介绍

占用率(nvprof 中的achieved occupancy):
占用率指的是活跃线程束与最大线程束的比率。活跃线程束足够多,可以保证并行性的充分执行(有利于延迟隐藏)。占用率达到一定高度,再增加也不会提高性能,所以占用率不是衡量性能的唯一标准。
延迟隐藏:一个线程束的延迟可以被其他线程束执行所隐藏。

线程束执行率(nvprof中的warp executation effeciency)
线程束中线程的执行

分支率(nvprof中的branch effeciency):
分支率是指未分化的分支数所有分支数的比率,可以理解为这个数值越高,并行执行能力越强。这里的未分化的分支,是相对于线程束分化而言,线程束分化是指在同一个线程束中的线程执行不同的指令,比如在核函数中存在的if/else这种条件控制语句。同一线程束中的线程执行相同的指令,性能是最好的。nvcc编译器能够优化短的if/else 条件语句的分化问题,也就是说,你可能看到有条件语句的核函数执行时的分支率为100%,这就是CUDA编译器的功劳。当然,对于很长的if/else条件语句一定会产生线程束分化,也就是说,分支率<100%;

避免线程束分化的方法:调整分支粒度适应线程束大小的整数倍

每个线程束的指令数(nvprof中instructions per warp):
每个线程束上执行指令的平均数

全局加载效率(nvprof中 global memory load effeciency):
被请求的全局加载吞吐量与所需的全局加载吞吐量的比率,可以衡量应用程序的加载操作利用设备内存带宽的程度

全局加载吞吐量(nvprof中 global load throughout):
检查内核的内存读取效率,更高的加载吞吐量不一定意味着更高的性能。

二、案例介绍

1. 案例说明

这里以整数规约(数据累加求和)为例,实现了三种不同的内核函数,交错规约性能最好。

reduceNeighbored 内核函数流程(下图引用《CUDA C 编程权威指南》):
在这里插入图片描述reduceNeighboredLess 内核函数流程(下图引用《CUDA C 编程权威指南》):
在这里插入图片描述reduceInterLeave 内核函数流程(下图引用《CUDA C 编程权威指南》):
在这里插入图片描述

2. 案例实现

#include <stdio.h>
#include <iostream>

#include <cuda.h>
#include <cuda_runtime.h>
#include <cuda_runtime_api.h>
#include <device_launch_parameters.h>
#include <device_functions.h>

#include "CudaUtils.h"

//cpu recursive reduce
int recursiveReduce(int* data, const int size)
{
	if (size == 1)
	{
		return data[0];
	}

	const int stride = size / 2;
	// in-place reduction
	for (int i = 0; i < stride; i++)
	{
		data[i] += data[i + stride];
	}

	//call recursively
	return recursiveReduce(data, stride);
}

//accumulate by neighbor elements of array
__global__ void reduceNeighbored(int* g_idata, int* g_odata, unsigned int n)
{
	//set thread ID
	unsigned int tid = threadIdx.x;

	//convert global data pointer to the local pointer of this block
	int* idata = g_idata + blockIdx.x * blockDim.x;

	//boundary check
	if (tid >= n)
		return;

	// in-place reduction in global memory
	for (int stride = 1; stride < blockDim.x; stride *= 2)
	{
		if (tid % (2 * stride) == 0)
		{
			idata[tid] += idata[tid + stride];
		}

		//synchronize within block, wait all threads finish within block
		__syncthreads();
	}

	//write result for this block to global mem
	if (tid == 0)
		g_odata[blockIdx.x] = idata[0];
}

//accumulate by neighbor elements of array
__global__ void reduceNeighboredLess(int* g_idata, int* g_odata, unsigned int n)
{
	//set thread ID
	unsigned int tid = threadIdx.x;
	unsigned int idx = threadIdx.x + blockIdx.x * blockDim.x;

	//convert global data pointer to the local pointer of this block
	int* idata = g_idata + blockIdx.x * blockDim.x;

	//boundary check
	if (idx >= n)
		return;

	// in-place reduction in global memory
	for (int stride = 1; stride < blockDim.x; stride *= 2)
	{
		int index= 2 * stride * tid;
		if (index < blockDim.x)
			idata[index] += idata[index + stride];

		//synchronize within block, wait all threads finish within block
		__syncthreads();
	}

	//write result for this block to global mem
	if (tid == 0)
		g_odata[blockIdx.x] = idata[0];
}

//accumulate by neighbor elements of array
__global__ void reduceInterLeave(int* g_idata, int* g_odata, unsigned int n)
{
	//set thread ID
	unsigned int tid = threadIdx.x;
	
	//convert global data pointer to the local pointer of this block
	int* idata = g_idata + blockIdx.x * blockDim.x;

	//boundary check
	if (tid >= n)
		return;

	// in-place reduction in global memory
	for (int stride = blockDim.x / 2; stride > 0; stride >>= 1)
	{
		if (tid < stride)
			idata[tid] += idata[tid + stride];

		//synchronize within block, wait all threads finish within block
		__syncthreads();
	}

	//write result for this block to global mem
	if (tid == 0)
		g_odata[blockIdx.x] = idata[0];
}

int main()
{
	int nDevId = 0;
	cudaDeviceProp stDeviceProp;
	cudaGetDeviceProperties(&stDeviceProp, nDevId);
	printf("device %d: %s\n", nDevId, stDeviceProp.name);
	cudaSetDevice(nDevId);

	bool bResult = false;

	//initialization
	int size = 1 << 24; //total number of elements to reduce
	printf("array size: %d \n", size);

	//execution configuration
	int nBlockSize = 512;// initial block size
	dim3 block(nBlockSize, 1);
	dim3 grid((size + block.x - 1) / block.x, 1);
	printf("grid: %d, block: %d\n", grid.x, block.x);

	//allocate host memory
	size_t bytes = size * sizeof(int);
	int* h_idata = (int*)malloc(bytes);
	int* h_odata = (int*)malloc(grid.x * sizeof(int));
	int* tmp = (int*)malloc(bytes);

	//initialize the array
	for (int i = 0; i < size; i++)
	{
		h_idata[i] = i;
	}
	memcpy(tmp, h_idata, bytes);

	double dElaps;
	int nGpuNum = 0;

	//allocate device memory
	int* d_idata = NULL;
	int* d_odata = NULL;
	cudaMalloc(&d_idata, bytes);
	cudaMalloc(&d_odata, grid.x * sizeof(int));

	//cpu reducation
	CudaUtils::Time::Start();
	int cpu_sum = recursiveReduce(tmp, size);
	CudaUtils::Time::End();
	dElaps = CudaUtils::Time::Duration<CudaUtils::Time::MS>();
	printf("cpu reduce: elapsed %.2f ms gpu_sum: %d\n",
		dElaps, cpu_sum);

	// kernel 0: warpup -- reduceNeighbored
	cudaMemcpy(d_idata, h_idata, bytes, cudaMemcpyHostToDevice);
	cudaDeviceSynchronize();

	CudaUtils::Time::Start();
	reduceNeighbored << <grid, block >> > (d_idata, d_odata, size);
	cudaDeviceSynchronize();
	CudaUtils::Time::End();
	dElaps = CudaUtils::Time::Duration<CudaUtils::Time::MS>();
	cudaMemcpy(h_odata, d_odata, grid.x * sizeof(int), cudaMemcpyDeviceToHost);
	
	size_t gpu_sum = 0;
	for (int i = 0; i < grid.x; i++)
		gpu_sum += h_odata[i];
	printf("gpu Warmup: elapsed %.2f ms gpu_sum: %lld\n",
		dElaps, gpu_sum);

	// kernel 1: reduceNeighbored
	cudaMemcpy(d_idata, h_idata, bytes, cudaMemcpyHostToDevice);
	cudaDeviceSynchronize();

	CudaUtils::Time::Start();
	reduceNeighbored << <grid, block >> > (d_idata, d_odata, size);
	cudaDeviceSynchronize();
	CudaUtils::Time::End();
	dElaps = CudaUtils::Time::Duration<CudaUtils::Time::MS>();
	cudaMemcpy(h_odata, d_odata, grid.x * sizeof(int), cudaMemcpyDeviceToHost);
	
	gpu_sum = 0;
	for (int i = 0; i < grid.x; i++)
		gpu_sum += h_odata[i];
	printf("gpu Neighbored: elapsed %.2f ms gpu_sum: %lld\n",
		dElaps, gpu_sum);

	// kernel 2: reduceNeighboredLess - 减少线程束分化
	cudaMemcpy(d_idata, h_idata, bytes, cudaMemcpyHostToDevice);
	cudaDeviceSynchronize();

	CudaUtils::Time::Start();
	reduceNeighboredLess << <grid, block >> > (d_idata, d_odata, size);
	cudaDeviceSynchronize();
	CudaUtils::Time::End();
	dElaps = CudaUtils::Time::Duration<CudaUtils::Time::MS>();
	cudaMemcpy(h_odata, d_odata, grid.x * sizeof(int), cudaMemcpyDeviceToHost);

	gpu_sum = 0;
	for (int i = 0; i < grid.x; i++)
		gpu_sum += h_odata[i];
	printf("gpu NeighboredLess: elapsed %.2f ms gpu_sum: %lld\n",
		dElaps, gpu_sum);

	// kernel 3: reduceInterLeave - 减少线程束分化
	cudaMemcpy(d_idata, h_idata, bytes, cudaMemcpyHostToDevice);
	cudaDeviceSynchronize();

	CudaUtils::Time::Start();
	reduceInterLeave << <grid, block >> > (d_idata, d_odata, size);
	cudaDeviceSynchronize();
	CudaUtils::Time::End();
	dElaps = CudaUtils::Time::Duration<CudaUtils::Time::MS>();
	cudaMemcpy(h_odata, d_odata, grid.x * sizeof(int), cudaMemcpyDeviceToHost);

	gpu_sum = 0;
	for (int i = 0; i < grid.x; i++)
		gpu_sum += h_odata[i];
	printf("gpu InterLeave: elapsed %.2f ms gpu_sum: %lld\n",
		dElaps, gpu_sum);

	//free host memory
	free(h_idata);
	free(h_odata);

	//free device memory
	cudaFree(d_idata);
	cudaFree(d_odata);


	system("pause");
	return 0;
}

3. 结果分析

在这里插入图片描述从运行时间看,reduceNeighbored内核函数最慢(线程束执行效率最低),reduceInterLeave内核函数最快(线程束执行效率最高)。


总结

衡量并行性的指标有很多,除了上面介绍的这些外,还有很多其他指标,通过均衡多个指标,评估并行能力,得到一个近似最优的网格和模块大小;通过后面的案例可以发现,最优的并行能力并不一定每一项衡量指标都是最优的。


参考资料

《CUDA C编程权威指南》

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CUDA C编程3 - 并行性衡量指标 的相关文章

随机推荐

  • 【SpringBoot高级篇】SpringBoot项目部署到docker环境中

    SpringBoot高级篇 SpringBoot项目部署到docker环境中 idea手动部署 1 创建springboot项目 1 1 POM xml 1 2 Controller 1 3 appplication yml 2 打包spr
  • eclipse debug后new 菜单只有 Project 、Example 、 Other 没有Java Project 、Package 、Class等的解决方法

    eclipse debug后new 菜单只有 Project Example Other 没有Java Project Package Class等的解决方法 解决办法 切换工作空间
  • 2023网安人才报告:网络安全科技人才市场需求规模快速增长

    7月24日 奇安信行业安全研究中心联合牛客平台 网教盟 新安盟 广州大学 深圳信息职业技术学院等单位 在BCS2023重庆网络与数据安全产业大会上共同发布了 2023网络安全人才市场状况研究报告 报告显示 在过去一年中 网络安全科技人才市场
  • 如何把文件传到华为云服务器,如何把文件传到云服务器

    如何把文件传到云服务器 内容精选 换一换 本节为您介绍如何在本机使用远程登录工具MSTSC登录Windows弹性云服务器 弹性云服务器状态为 运行中 如果弹性云服务器采用密钥方式鉴权 已获取Windows弹性云服务器的密码 获取方式请参见获
  • 电脑主板跳线_电脑哥教你如何接电脑主板跳线

    主板跳线接法详解 图 作为一名新手 要真正从头组装好自己的电脑并不容易 也许你知道CPU应该插哪儿 内存应该插哪儿 但遇到一排排复杂跳线的时候 很多新手都不知道如何下手 钥匙开机其实并不神秘 还记不记得你第一次见到装电脑的时候 JS将CPU
  • stm32f407 usb cdc设备无法启动问题

    最新要做一个项目 要求基于STM32F407实现USB CDC设备 首先想到的就是直接用STM32CUBEMX工具来生成 OK 话不多说 直接上过程 RCC配置 Sys配置 USB OTG FS配置 USB DEVICE配置 时钟配置 然后
  • Windows 下安装并配置Maven

    前言 Maven 翻译为 专家 内行 是Apache下的一个纯Java开发的开源项目 Maven 是一个项目管理工具 可以对Java项目进行构建 依赖管理 Maven是基于项目对象模型 POM project object model 可以
  • 输入阻抗与偏置电流

    对于高阻信号要选用FET运放 高阻信号R2和运放上的高阻并联会影响实际R2电阻值 CMRR 共模抑制比 放大电路对差模信号的电压增益与对共模信号的电压增益之比的绝对值 因为我们要抑制零漂所以共模电压增益越小越好 而差模电压增益越大越好 所以
  • pycharm上已存在某些库,但无法调用的问题解决

    如果出现pycharm上已存在openpyxl 但无法调用的时候 就是pycharm安装openpyxl库时 安装的位置与pycharm中引用python解释器的位置不一致 导致不能调用opentyxl 所以再安装openpyxl时 把项目
  • Python 绝对简明手册

    原文 简述 1 阅读须知 文中使用 gt gt gt 作为会命令行中的输出信息的前缀 对于不清楚用用途的函数可以在解释器下面输入 help 函数名 来获取相关信息 另外 自带的文档和google也是不可少的 2 基本语法 2 1 if el
  • 简历制作讲解

    简历制作讲解 前期假想 简历如同一本书 书大体分为文本结构和文本内容 一 简历文本结构 一 个人信息 必要 二 教育背景 必要 三 自我介绍 可选 四 工作经历 五 项目经历 六 技能评价 二 简历文本内容 一 个人信息 必要 必要信息 姓
  • WebTestClient使用

    介绍 WebTestClient用于测试WebFlux服务器端点的主要入口点 它具有与WebClient非常相似的API 内部大部分调用WebClient实例 主要提供测试上下文 绑定到一个服务 WebTestClient testClie
  • 解决:参考的对象类型不支持尝试的操作。 [已退出进程,代码为 4294967295]

    问题描述 win10系统下运行wsl时候显示错误 参考的对象类型不支持尝试的操作 已退出进程 代码为 4294967295 经过个人测试解决方式为关闭网易UU当前的加速 过一会就恢复正常 不需要重启
  • MySQL架构的Server层的执行过程

    1 连接器 主要负责跟客户端建立连接 获取权限 维持和管理连接 2 查询缓存 优先在缓存中进行查询 如果查到了则直接返回 如果缓存中查询不到 在去数据库中查询 3 解析器 分析器 分析器的工作主要是对要执行的SQL语句进行词法解析 语法解析
  • 基于SpringBoot+Async注解整合多线程

    提示 本文没有使用原生的创建线程方式 默认已掌握创建线程的四种方式 全文基于SpringBoot框架 要求读者掌握SpringBoot操作 本人能力有限 如有遗漏或错误 敬请指正 谢谢 文章目录 其他文章 前言 一 为什么要使用多线程 二
  • 计算机 服装生产管理的变化,服装生产管理概述.doc

    PAGE PAGE 182 目 录 TOC o n h z HYPERLINK l To 第一章 服装生产管理概述 HYPERLINK l To 第一节 服装生产概述 HYPERLINK l To 一 服装生产企业的特点 HYPERLINK
  • Yii 2.0集成七牛云

    背景知识 七牛云就是我们常说的图床 什么是图床 可以简单理解为是一种存储图片资源的服务器 本文基于Yii2简单介绍七牛云的使用 1 首先在七牛云平台创建账户 传送门 2 登陆账户之后 点击头部菜单管理控制台 进入之后 点击左侧菜单存储对象
  • 技术岗-网上测评智力题

    A 逻辑推理 1 你让工人为你工作7天 给工人的回报是一根金条 金条平分成相连的7段 你必须在每天结束时给他们一段金条 如果只许你两次把金条弄断 你如何给你 的工人付费 2 请把一盒蛋糕切成8份 分给8个人 但蛋糕盒里还必须留有一份 3 小
  • Qt Plugin

    问题 创建 Qt 插件 方法 1 QML 插件 1 qmldir plugin dll plugin qml 位于同一目录 目录名和模块名相同 2 错误列表如下 no dir no qmldir module module is not i
  • CUDA C编程3 - 并行性衡量指标

    系列文章目录 文章目录 系列文章目录 前言 一 CUDA C并行性衡量指标介绍 二 案例介绍 1 案例说明 2 案例实现 3 结果分析 总结 参考资料 前言 CUDA编程 就是利用GPU设备的并行计算能力实现程序的高速执行 CUDA内核函数