CUDA C编程权威指南:2.2-给核函数计时
本文主要通过例子介绍了如何给核函数计时的思路和实现。实现例子代码参考文献[7],只需要把相应章节对应的CMakeLists.txt文件拷贝到CMake项目根目录下面即可运行。
1.用CPU计时器计时(sumArraysOnGPU-timer.cu)[7]
在主函数中用CPU计时器测试向量加法的核函数,如下所示:
#include <cuda_runtime.h> // 包含cuda运行时系统的头文件
#include <stdio.h> // 包含标准输入输出函数的头文件
#include <time.h> // 包含时间函数的头文件
#include <sys/timeb.h> // 包含时间函数的头文件
//#define CHECK(call) // 定义CHECK宏函数
void initialData(float *ip, int size)
{
// 为随机数生成不同的种子
time_t t; // time_t是一种时间类型
srand((unsigned int) time(&t)); // time()函数返回当前时间的秒数
for (int i = 0; i < size; i++) // 生成随机数
{
ip[i] = (float) (rand() & 0xFF) / 10.0f; // rand()函数用于生成随机数
}
}
void checkResult(float *hostRef, float *gpuRef, const int N) // 检查结果
{
double epsilon = 1.0E-8; // 定义误差范围
bool match = 1; // 定义匹配标志
for (int i = 0; i < N; i++) // 比较每个元素
{
if (abs(hostRef[i] - gpuRef[i]) > epsilon) // 如果误差超过范围
{
match = 0; // 匹配标志置0
printf("Arrays do not match!\n"); // 打印提示信息
printf("host %5.2f gpu %5.2f at current %d\n", hostRef[i], gpuRef[i], i); // 打印不匹配的元素
break;
}
}
if (match) printf("Arrays match.\n\n"); // 如果匹配,打印提示信息
}
void sumArraysOnHost(float *A, float *B, float *C, const int N) // 在主机上计算
{
for (int idx = 0; idx < N; idx++) // 计算每个元素
{
C[idx] = A[idx] + B[idx]; // 计算
}
}
__global__ void sumArraysOnGPU(float *A, float *B, float *C) // 在设备上计算
{
// int i = threadIdx.x; // 获取线程索引
int i = blockIdx.x * blockDim.x + threadIdx.x; // 获取线程索引
// printf("threadIdx.x: %d, blockIdx.x: %d, blockDim.x: %d\n", threadIdx.x, blockIdx.x, blockDim.x); // 打印线程索引
C[i] = A[i] + B[i]; // 计算
}
struct timeval { // 定义timeval结构体
long tv_sec; // 秒
long tv_usec; // 微秒
};
int gettimeofday(struct timeval *tp, void *tzp) { // 定义gettimeofday函数
struct _timeb timebuffer; // 定义_timeb结构体
_ftime(&timebuffer); // 获取当前时间
tp->tv_sec = static_cast<long>(timebuffer.time); // 转换为秒
tp->tv_usec = timebuffer.millitm * 1000; // 转换为微秒
return 0; // 返回0
}
double cpuSecond() { // 定义cpuSecond函数
struct timeval tp; // 定义timeval结构体
gettimeofday(&tp, NULL); // 获取当前时间
return ((double) tp.tv_sec + (double) tp.tv_usec * 1.e-6); // 返回当前时间
}
int main(int argc, char** argv) {
printf("%s Starting...\n", argv[0]); // 打印提示信息
// 设置device
int dev = 0; // 定义device
cudaDeviceProp deviceProp; // 定义deviceProp结构体
// CHECK(cudaGetDeviceProperties(&deviceProp, dev)); // 获取deviceProp结构体
cudaGetDeviceProperties(&deviceProp, dev); // 获取deviceProp结构体
printf("Using Device %d: %s\n", dev, deviceProp.name);
// CHECK(cudaSetDevice(dev)); // 设置device
cudaSetDevice(dev); // 设置device
// 设置vector数据大小
int nElem = 1 << 24; // 定义vector大小,左移24位相当于乘以2的24次方
printf("Vector size %d\n", nElem); // 打印vector大小
// 分配主机内存
size_t nBytes = nElem * sizeof(float);
float *h_A, *h_B, *hostRef, *gpuRef; // 定义主机内存指针
h_A = (float *) malloc(nBytes); // 分配主机内存
h_B = (float *) malloc(nBytes); // 分配主机内存
hostRef = (float *) malloc(nBytes); // 分配主机内存,用于存储host端计算结果
gpuRef = (float *) malloc(nBytes); // 分配主机内存,用于存储device端计算结果
// 定义计时器
double iStart, iElaps;
// 初始化主机数据
iStart = cpuSecond(); // 记录开始时间
initialData(h_A, nElem); // 初始化数据
initialData(h_B, nElem); // 初始化数据
iElaps = cpuSecond() - iStart; // 记录结束时间
memset(hostRef, 0, nBytes); // 将hostRef清零
memset(gpuRef, 0, nBytes); // 将gpuRef清零
// 在主机做向量加法
iStart = cpuSecond(); // 记录开始时间
sumArraysOnHost(h_A, h_B, hostRef, nElem); // 在主机上计算
iElaps = cpuSecond() - iStart; // 记录结束时间
printf("sumArraysOnHost Time elapsed %f sec\n", iElaps); // 打印执行时间
// 分配设备全局内存
float *d_A, *d_B, *d_C;
cudaMalloc((float **) &d_A, nBytes);
cudaMalloc((float **) &d_B, nBytes);
cudaMalloc((float **) &d_C, nBytes);
// 拷贝数据到设备
cudaMemcpy(d_A, h_A, nBytes, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, nBytes, cudaMemcpyHostToDevice);
// 在设备端调用kernel
int iLen = 1024;
dim3 block(iLen);
dim3 grid((nElem + block.x - 1) / block.x);
iStart = cpuSecond(); // 记录开始时间
sumArraysOnGPU<<<grid, block>>>(d_A, d_B, d_C); // 调用kernel
cudaDeviceSynchronize(); // 同步device
iElaps = cpuSecond() - iStart; // 记录结束时间
printf("sumArraysOnGPU <<<%d, %d>>> Time elapsed %f sec\n", grid.x, block.x, iElaps); // 打印执行时间
// 拷贝结果到主机
cudaMemcpy(gpuRef, d_C, nBytes, cudaMemcpyDeviceToHost);
// 检查结果
checkResult(hostRef, gpuRef, nElem);
// 释放设备内存
cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);
// 释放主机内存
free(h_A);
free(h_B);
free(hostRef);
free(gpuRef);
return 0;
}
输出结果,如下所示:
Using Device 0: NVIDIA GeForce RTX 3090
Vector size 16777216
sumArraysOnHost Time elapsed 0.059000 sec
sumArraysOnGPU <<<16384, 1024>>> Time elapsed 0.001000 sec
Arrays match.
2.用nvprof工具计时(已过时)
nvprof命令行工具可以从应用程序的CPU和GPU活动中获取时间线信息,包括内核执行、内存传输以及CUDA API的调用。使用语法如下所示:
$ nvprof [nvprof_args] <application> [application_args]
$ nvprof --help
$ nvprof ./sumArraysOnGPU-timer
遇到这个错误,将C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\extras\CUPTI\lib64添加到Path环境变量中即可,如下所示:



说明:对于计算能力为8.0及更高版本的设备,不支持使用nvprof。对于GPU跟踪和CPU采样,使用NVIDIA Nsight Systems;对于GPU分析,使用NVIDIA Nsight Compute。
这部分还不熟悉,先简要介绍,后续深入研究。Nsight是NVIDIA面相开发者提供的开发工具套件,能提供深入的跟踪、调试、评测和分析,以优化跨NVIDIA GPU和CPU的复杂计算应用程序。Nsight主要包含Nsight System、Nsight Compute、Nsight Graphics三部分。
(1)Nsight System
所有与NVIDIA GPU相关的程序开发都可以从Nsight System开始以确定最大的优化机会。Nsight System给开发者一个系统级别的应用程序性能的可视化分析。开发人员可以优化瓶颈,以便在任意数量或大小的CPU和GPU之间实现高效扩展[4]。
(2)Nsight Compute
Nsight Compute是一个CUDA应用程序的交互式kernel分析器。它通过用户接口和命令行工具的形式提供了详细的性能分析度量和API调试。Nsight Compute还提供了定制化的和数据驱动的用户接口和度量集合,可以使用分析脚本对这些界面和度量集合进行扩展,以获得后处理的结果[5]。
(3)Nsight Graphics
Nsight Graphics是一个用于调试、评测和分析Microsoft Windows和Linux上的图形应用程序。它允许您优化基于Direct3D 11,Direct3D 12,DirectX,Raytracing 1.1,OpenGL,Vulkan和KHR Vulkan Ray Tracing Extension的应程序的性能[6]。
3.CUDA数据类型
CUDA支持多种数据类型,包括标准的C/C++数据类型以及特定于CUDA的数据类型。常见的CUDA数据类型,如下所示:
(1)基本数据类型(与C/C++相似)
int
float
double
char
unsigned int
short
long
unsigned char
unsigned short
unsigned long
bool
void
signed int或signed
signed char
signed short
signed long
signed long long
unsigned long long
(2)向量数据类型
int1, int2, int3, int4: 1到4个整数元素的向量。
float1, float2, float3, float4: 1到4个浮点数元素的向量。
double1, double2, double3, double4: 1到4个双精度浮点数元素的向量。
(3)复数数据类型
cuComplex: 单精度复数。
cuDoubleComplex: 双精度复数。
(4)CUDA数据类型修饰符
__device__: 用于声明在设备上执行的函数或变量。
__constant__: 用于声明常量内存。
__shared__: 用于声明共享内存。
__global__: 用于声明全局内核函数。
(5)CUDA特定的数据类型
dim3: 三维坐标,通常用于线程和块的索引。
cudaStream_t: 用于管理CUDA流。
cudaEvent_t: 用于事件同步。
texture<...>: 用于声明纹理内存,支持不同的数据类型和维度。
除此之外CUDA还支持通过typedef和struct等方式定义自定义数据类型,以满足特定应用程序的需求。
4.C++数据类型
C++中数据类型完整列表,包括基本数据类型、复合数据类型和一些特殊数据类型,如下所示:
(1)基本数据类型
int
float
double
char
bool
void
wchar_t(宽字符类型)
short(或short int)
long(或long int)
long long(或long long int)
unsigned int
unsigned char
unsigned short
unsigned long
unsigned long long
(2)复合数据类型
array(数组)
struct(结构体)
union(联合体)
enum(枚举)
class(类)
(3)其它数据类型
signed(通常与int、char、short和long配合使用,表示有符号类型)
auto(C++11引入的自动类型推导)
decltype(C++11引入的类型推导)
nullptr(C++11引入的空指针)
5.通过deviceQuery.exe查下设备信息
因为本地安装CUDA 11.8版本,所以下载https://github.com/NVIDIA/cuda-samples/releases/tag/v11.8,然后使用VS2022打开Samples_VS2022.sln编译,如下所示:

执行.\deviceQuery.exe后可以看到很多熟面孔的常量信息,如下所示:

参考文献:
[1]NVIDIA Developer Tools:https://developer.nvidia.com/tools-overview
[2]《CUDA C编程权威指南》
[3]使用Nsight工具分析优化应用程序:https://cloud.baidu.com/doc/GPU/s/el8mizux4
[4]https://developer.nvidia.com/nsight-systems
[5]https://developer.nvidia.com/nsight-compute
[6]https://developer.nvidia.com/nsight-graphics
[7]给核函数计时:https://github.com/ai408/nlp-engineering/tree/main/20230917_NLP工程化/20231004_高性能计算/20231003_CUDA编程/20231003_CUDA_C编程权威指南/2-CUDA编程模型/2.2-给核函数计时
CUDA C编程权威指南:2.2-给核函数计时的更多相关文章
- 『CUDA C编程权威指南』第二章编程题选做
第一题 设置线程块中线程数为1024效果优于设置为1023,且提升明显,不过原因未知,以后章节看看能不能回答. 第二题 参考文件sumArraysOnGPUtimer.cu,设置block=256,新 ...
- 读《Android编程权威指南》
因为去年双十二购买了一折的<Android 编程权威指南(第一版)>,在第二版出来后图灵社区给我推送了第二版的优惠码,激动之余就立马下单购买电子书,不得不说Big Nerd Ranch G ...
- 《Android编程权威指南》
<Android编程权威指南> 基本信息 原书名:Android programming: the big nerd ranch guide 原出版社: Big Nerd Ranch Gu ...
- Swift编程权威指南第2版 读后收获
自从参加工作一直在用OC做iOS开发.在2015年的时候苹果刚推出swift1.0不久,当时毕竟是新推出的语言,大家也都很有激情的学习.不过在学完后发现很难在实际项目中使用,再加上当时公司项目都是基于 ...
- 《Android编程权威指南》PhotoGallery应用梳理
PhotoGalley是<Android编程权威指南>书中另外一个重要的应用.
- 《Android编程权威指南》CriminalIntent项目梳理
相信很多新手或者初级开发人员都已经买了第2版的<Android编程权威指南>, 这本书基于Android Studio开发,对入门人员来说是很好的选择,但是很可惜的是, 在完成一个项目后, ...
- 使用最新AndroidStudio编写Android编程权威指南(第3版)中的代码会遇到的一些问题
Android编程权威指南(第3版)这本书是基于Android7.0的,到如今已经过于古老,最新的Android版本已经到10,而这本书的第四版目前还没有正式发售,在最近阅读这本书时,我发现这本书的部 ...
- Android编程权威指南第三版 第32章
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/qq_35564145/article/de ...
- Android编程权威指南(第2版)--第16章 使用intent拍照 挑战练习
16.7挑战练习:优化照片显示 新建dialog_photo.xml 1234567891011121314 <?xml version="1.0" encoding=&qu ...
- Android编程权威指南(第三版)- 2.8 挑战练习:添加后退按钮
package com.example.geoquiz; import android.support.v7.app.AppCompatActivity; import android.os.Bund ...
随机推荐
- JavaScript判断两个数组相等的四类方法
在JavaScript中,数组本质上是一种特殊的对象,它的类型值会返回 object. 如果我们需要比较两个数组是否相等,不能像比较基本类型(String.Number.Boolean等)一样,使用 ...
- 未来的编程语言「GitHub 热点速览」
又一个编程语言火了,不算新,因为它已经开发了一段时间.不过在本周 Hacker News 上风头十足,DreamBerd 除了有点意思的改 ; 分隔符为 !,之外,它还能让你用问号来标注一段你也不确定 ...
- 基于C# 开发的SOL SERVER 操作数据库类(SQLHelp)
说明:以下是我近两年年来开发中最常用的C#操作sql server数据库访问类,对初学者非常有用,容易扩展,支持多库操作,多研究研究,有什么问题欢迎留言 当前环境为 C# .NET CORE 3.0 ...
- AI 时代的视频云转码移动端化——更快、更好、更低、更广
编者按: AI技术的落地是渐渐地从服务器端.云端落地,逐步到移动端及边缘设备上.这些年随着AI技术的进步,轻量级算法模型开始在移动端实时跑起来,并且移动端算法也在不断进行迭代和完善,而对于实时直播场景 ...
- 【Azure API Management】实现在API Management服务中使用MI(管理标识 Managed Identity)访问启用防火墙的Storage Account
问题描述 在Azure的同一数据中心,API Management访问启用了防火墙的Storage Account,并且把APIM的公网IP地址设置在白名单.但访问依旧是403 原因是: 存储帐户部署 ...
- Flutter ncnn 使用
Flutter 实现手机端 App,如果想利用 AI 模型添加新颖的功能,那么 ncnn 就是一种可考虑的手机端推理模型的框架. 本文即是 Flutter 上使用 ncnn 做模型推理的实践分享.有如 ...
- 2023年郑州轻工业大学校赛邀请赛jc
比赛时,jxh和myh从头开始看题,我拿着试题册去找签到,很快他们签上了一个数学题,我跟他们说兔子和飞镖可以写,刚开始飞镖这个题我先wa了一次,因为刚开始的思路少考虑了情况,我们队后来改的挺乱,jxh ...
- 【ElasticSearch】大数据量情况下的前缀、中缀实时搜索方案
简述 业务开发中经常会遇到这样一种情况,用户在搜索框输入时要实时展示搜索相关的结果.要实现这个场景常用的方案有Completion Suggester.search_as_you_type.那么这两种 ...
- java 线程等待和唤醒方法
java线程状态变迁图 从图中可以看出Java 线程等待方法是将线程从Runnable状态转换为Waiting状态,Java线程的唤醒方法是将线程从Waiting状态唤醒进入Runnable状态 在J ...
- Unsupervised Learning of Depth and Ego-Motion from Video(CVPR2017)论文阅读
深度估计问题 从输入的单目或双目图像,计算图像物体与摄像头之间距离(输出距离图),双目的距离估计应该是比较成熟和完善,但往单目上考虑主要还是成本的问题,所以做好单目的深度估计有一定的意义.单目的意思是 ...