CUDA时长统计
技术背景
前面的一篇文章中介绍了在CUDA中使用宏来监测CUDA C函数或者Kernel函数的运行报错问题。同样的思路,我们可用写一个用于统计函数运行时长的宏,这样不需要使用额外的工具来对函数体的性能进行测试。
文件准备
因为这里的宏改动,主要涉及CUDA头文件和CUDA文件的修改,所以Cython文件和Python文件还有异常捕获宏我们还是复用这篇文章里面用到的。测试内容是,定义一个原始数组和一个索引数组,输出索引的结果数组。
wrapper.pyx
# cythonize -i -f wrapper.pyx
import numpy as np
cimport numpy as np
cimport cython
cdef extern from "<dlfcn.h>" nogil:
void *dlopen(const char *, int)
char *dlerror()
void *dlsym(void *, const char *)
int dlclose(void *)
enum:
RTLD_LAZY
ctypedef int (*GatherFunc)(float *source, int *index, float *res, int N, int M) noexcept nogil
cdef void* handle = dlopen('/path/to/libcuindex.so', RTLD_LAZY)
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef float[:] cuda_gather(float[:] x, int[:] idx):
cdef:
GatherFunc Gather
int success
int N = idx.shape[0]
int M = x.shape[0]
float[:] res = np.zeros((N, ), dtype=np.float32)
Gather = <GatherFunc>dlsym(handle, "Gather")
success = Gather(&x[0], &idx[0], &res[0], N, M)
return res
while not True:
dlclose(handle)
test_gather.py
import numpy as np
np.random.seed(0)
from wrapper import cuda_gather
M = 1024 * 1024 * 128
N = 1024 * 1024
x = np.random.random((M,)).astype(np.float32)
idx = np.random.randint(0, M, (N,)).astype(np.int32)
res = np.asarray(cuda_gather(x, idx))
print (res.shape)
print ((res==x[idx]).sum())
error.cuh
#pragma once
#include <stdio.h>
#define CHECK(call) do{const cudaError_t error_code = call; if (error_code != cudaSuccess){printf("CUDA Error:\n"); printf(" File: %s\n", __FILE__); printf(" Line: %d\n", __LINE__); printf(" Error code: %d\n", error_code); printf(" Error text: %s\n", cudaGetErrorString(error_code)); exit(1);}} while (0)
计时宏
这里增加一个用于计时的record.cuh头文件,里面写一个TIME_CUDA_FUNCTION宏,然后在CUDA中需要统计的函数前调用,就可以输出CUDA函数的运行时长了。
#pragma once
#include <stdio.h>
#include <cuda_runtime.h>
// 宏定义,用于测量CUDA函数的执行时间
#define TIME_CUDA_FUNCTION(func) \
do { \
cudaEvent_t start, stop; \
float elapsedTime; \
cudaEventCreate(&start); \
cudaEventCreate(&stop); \
cudaEventRecord(start, NULL); \
\
func; \
\
cudaEventRecord(stop, NULL); \
cudaEventSynchronize(stop); \
cudaEventElapsedTime(&elapsedTime, start, stop); \
printf("Time taken by function %s is: %f ms\n", #func, elapsedTime); \
\
cudaEventDestroy(start); \
cudaEventDestroy(stop); \
} while (0)
计时宏的使用
我们在CUDA文件cuda_index.cu中调用record.cuh里面的计时宏,这里用来统计一个CUDA核函数的执行时间:
// nvcc -shared ./cuda_index.cu -Xcompiler -fPIC -o ./libcuindex.so
#include <stdio.h>
#include "cuda_index.cuh"
#include "error.cuh"
#include "record.cuh"
void __global__ GatherKernel(float *source, int *index, float *res, int N){
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < N){
res[idx] = source[index[idx]];
}
}
extern "C" int Gather(float *source, int *index, float *res, int N, int M){
float *souce_device, *res_device;
int *index_device;
CHECK(cudaMalloc((void **)&souce_device, M * sizeof(float)));
CHECK(cudaMalloc((void **)&res_device, N * sizeof(float)));
CHECK(cudaMalloc((void **)&index_device, N * sizeof(int)));
CHECK(cudaMemcpy(souce_device, source, M * sizeof(float), cudaMemcpyHostToDevice));
CHECK(cudaMemcpy(res_device, res, N * sizeof(float), cudaMemcpyHostToDevice));
CHECK(cudaMemcpy(index_device, index, N * sizeof(int), cudaMemcpyHostToDevice));
int block_size = 1024;
int grid_size = (N + block_size - 1) / block_size;
TIME_CUDA_FUNCTION((GatherKernel<<<grid_size, block_size>>>(souce_device, index_device, res_device, N)));
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
CHECK(cudaMemcpy(res, res_device, N * sizeof(float), cudaMemcpyDeviceToHost));
CHECK(cudaFree(souce_device));
CHECK(cudaFree(index_device));
CHECK(cudaDeviceSynchronize());
CHECK(cudaFree(res_device));
CHECK(cudaDeviceReset());
return 1;
}
需要注意的是,TIME_CUDA_FUNCTION宏只能有一个输入,但是使用CUDA核函数的时候实际上会被当作是两个输入,因此我们需要将CUDA核函数用括号再封装起来。
输出结果
最终按照这篇文章中的运行流程,可以得到这样的输出结果:
Time taken by function (GatherKernel<<<grid_size, block_size>>>(souce_device, index_device, res_device, N)) is: 0.584224 ms
(1048576,)
1048576
这里CUDA核函数的运行时长被正确的格式化输出了。
返回耗时数值
除了在CUDA中直接打印耗时的数值,我们还可以修改record.cuh中的宏,让其返回耗时数值:
#pragma once
#include <stdio.h>
#include <cuda_runtime.h>
// 宏定义,用于测量CUDA函数的执行时间
#define TIME_CUDA_FUNCTION(func) \
do { \
cudaEvent_t start, stop; \
float elapsedTime; \
cudaEventCreate(&start); \
cudaEventCreate(&stop); \
cudaEventRecord(start, NULL); \
\
func; \
\
cudaEventRecord(stop, NULL); \
cudaEventSynchronize(stop); \
cudaEventElapsedTime(&elapsedTime, start, stop); \
printf("Time taken by function %s is: %f ms\n", #func, elapsedTime); \
\
cudaEventDestroy(start); \
cudaEventDestroy(stop); \
} while (0)
// 宏定义,用于测量CUDA函数的执行时间并返回该时间
#define GET_CUDA_TIME(func) \
({ \
cudaEvent_t start, stop; \
float elapsedTime = 0.0f; \
cudaEventCreate(&start); \
cudaEventCreate(&stop); \
cudaEventRecord(start, NULL); \
\
func; \
\
cudaEventRecord(stop, NULL); \
cudaEventSynchronize(stop); \
cudaEventElapsedTime(&elapsedTime, start, stop); \
\
cudaEventDestroy(start); \
cudaEventDestroy(stop); \
\
elapsedTime; \
})
修改头文件cuda_index.cuh,因为这里我们需要返回一个运行时长的float数值,不再是int类型了:
#include <stdio.h>
extern "C" float Gather(float *source, int *index, float *res, int N, int M);
最后再对应修改下cuda_index.cu中的内容:
// nvcc -shared ./cuda_index.cu -Xcompiler -fPIC -o ./libcuindex.so
#include <stdio.h>
#include "cuda_index.cuh"
#include "error.cuh"
#include "record.cuh"
void __global__ GatherKernel(float *source, int *index, float *res, int N){
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < N){
res[idx] = source[index[idx]];
}
}
extern "C" float Gather(float *source, int *index, float *res, int N, int M){
float *souce_device, *res_device;
int *index_device;
CHECK(cudaMalloc((void **)&souce_device, M * sizeof(float)));
CHECK(cudaMalloc((void **)&res_device, N * sizeof(float)));
CHECK(cudaMalloc((void **)&index_device, N * sizeof(int)));
CHECK(cudaMemcpy(souce_device, source, M * sizeof(float), cudaMemcpyHostToDevice));
CHECK(cudaMemcpy(res_device, res, N * sizeof(float), cudaMemcpyHostToDevice));
CHECK(cudaMemcpy(index_device, index, N * sizeof(int), cudaMemcpyHostToDevice));
int block_size = 1024;
int grid_size = (N + block_size - 1) / block_size;
float timeTaken = GET_CUDA_TIME((GatherKernel<<<grid_size, block_size>>>(souce_device, index_device, res_device, N)));
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
CHECK(cudaMemcpy(res, res_device, N * sizeof(float), cudaMemcpyDeviceToHost));
CHECK(cudaFree(souce_device));
CHECK(cudaFree(index_device));
CHECK(cudaDeviceSynchronize());
CHECK(cudaFree(res_device));
CHECK(cudaDeviceReset());
return timeTaken;
}
这样就可以把函数运行耗时的数值返回给Cython文件,然后在Cython文件wrapper.pyx中打印耗时:
# cythonize -i -f wrapper.pyx
import numpy as np
cimport numpy as np
cimport cython
cdef extern from "<dlfcn.h>" nogil:
void *dlopen(const char *, int)
char *dlerror()
void *dlsym(void *, const char *)
int dlclose(void *)
enum:
RTLD_LAZY
ctypedef float (*GatherFunc)(float *source, int *index, float *res, int N, int M) noexcept nogil
cdef void* handle = dlopen('/home/dechin/projects/gitee/dechin/tests/cuda/libcuindex.so', RTLD_LAZY)
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef float[:] cuda_gather(float[:] x, int[:] idx):
cdef:
GatherFunc Gather
float timeTaken
int N = idx.shape[0]
int M = x.shape[0]
float[:] res = np.zeros((N, ), dtype=np.float32)
Gather = <GatherFunc>dlsym(handle, "Gather")
timeTaken = Gather(&x[0], &idx[0], &res[0], N, M)
print (timeTaken)
return res
while not True:
dlclose(handle)
最后再通过Python模块调用(无需改动),输出结果为:
0.6107839941978455
(1048576,)
1048576
这里的单位是ms。
总结概要
这篇文章主要介绍了一个CUDA入门的技术:使用CUDA头文件写一个专门用于CUDA函数运行时长统计的宏,这样就可以统计目标Kernel函数的运行时长。可以直接在CUDA中打印相应的数值,也可以回传到Cython或者Python中进行打印。
版权声明
本文首发链接为:https://www.cnblogs.com/dechinphy/p/cuda-time-record.html
作者ID:DechinPhy
更多原著文章:https://www.cnblogs.com/dechinphy/
请博主喝咖啡:https://www.cnblogs.com/dechinphy/gallery/image/379634.html
CUDA时长统计的更多相关文章
- Fragment时长统计那些事
注:本文同步发布于微信公众号:stringwu的互联网杂谈 frament时长统计那些事 页面停留时长作为应用统计的北极星指标里的重要指标之一,统计用户在某个页面的停留时长则变得很重要.而Fragme ...
- java 多线程执行时长统计
ExecutorService——shutdown方法和awaitTermination方法 shutdown方法:平滑的关闭ExecutorService,当此方法被调用时,ExecutorServ ...
- windows 7 下,如何统计某文件夹下 视频总时长
由于项目需要,我需要给系统加权限,这真是一个让人头疼的问题,如果要每个业务方法都加上权限判断逻辑,那真的不敢想象是多么大的工作量,日后有变动的话,我会不会发疯? 所以我必须利用之前学到的AOP编程,在 ...
- 使用opencv统计视频库的总时长
统计视频库里的视频文件的总时长 废话不多说,直接上代码: /* * ================================================================== ...
- 统计 flv视频总时长
在学习孟媛的视频课程.网上能下载的是flv格式.那我在学习之前,我要统计一下这个课程的数量,他会用多长时间,这样方便我在学习过程中不断的回顾,进行时间管理.我大概就可以统计出来这个视频多长时间可以学完 ...
- 使用mediainfo工具统计每个视频文件(媒体文件)播放时长
需求 1.运营那边需要统计大量视频文件的播放时长,并汇总记录到excel表中,问我有什么方法搞定 这边搜索了很多统计媒体文件时长的,主要有以下几种 1.使用java获取 2.使用python获取 3. ...
- Hexo添加字数统计、阅读时长
统计插件 配置 NexT 主题默认已经集成了文章[字数统计].[阅读时长]统计功能,如果我们需要使用,只需要在主题配置文件 _config.yml 中打开 wordcount 统计功能即可.如下所示: ...
- bash 统计在线时长最长的十个玩/统计一天内一直处于不活跃状态的玩家的百分比
1.某游戏的客户端每隔5分钟会向服务端报告一次玩家的账户积分,如果两次报告的时间间隔不大于5分钟,认为该玩家在这5分钟内在线,假设报告数据的格式如下: IP Dat ...
- iNeuOS工业互联网操作系统,脚本化实现设备运行时长和效率计算与统计
目 录 1. 概述... 2 2. 实时采集开停状态... 2 3. 增加虚拟设备... 2 4. 脚本统计和计算设备运行时长... 4 5. ...
- js用img代替ajax js心跳 向服务器定时传送参数 主要计算用户在线时长
html: <!doctype html><html><head><meta charset="utf-8"><title&g ...
随机推荐
- k3s 轻量级Kubernetes 安装实例
k3s是由Rancher开发的轻量级Kubernetes,支持嵌入式系统,边缘计算节点等 易于安装,所有组件都在一个小于100MB的二进制文件中,占用资源低 1.1.简单安装试例 curl -sfL ...
- Java 项目愚蠢的分层及解决方案
<整洁架构之道>的最后一章<细节决定成败>又在讨论 Javaer 永恒的问题:分层后 DAO Service Controller 应该按功能分包还是按层分包. 按功能分包的人 ...
- 免费学习基于SpringBoot的高考志愿智能推荐系统
免费学习基于SpringBoot的高考志愿智能推荐系统 摘要 科学技术日新月异,人们的生活都发生了翻天覆地的变化,高考志愿智能推荐系统管理当然也不例外.过去的信息管理都使用传统的方式实行,既花费了时间 ...
- Qt编写linux上视频流播放器(支持海康大华宇视等各种网络摄像机)
一.前言 在windows上的视频流播放器有很多,而且各个监控厂家无论大厂还是小厂,基本上都提供了客户端,甚至很多第三方的监控平台软件厂商,也都提供了windows的版本,基本的都没有提供linux版 ...
- Qt数据库应用23-个人信息报表
一.前言 自从上次做完的图文报表,又新来了个需求需要实现个人信息报表,类似个人简历一样的格式,数据从数据库中取出来,然后一个人的信息就打印一张,传入查询的多个人员信息,自动分页打印个人信息报表,报表可 ...
- Qt开发经验小技巧161-165
经常有人说Qt垃圾,说用Qt在1毫秒绘制几千个数据点卡成屎.其实显示器最高刷新频率一般才60帧,1毫秒就绘制一次有意义吗?不仅显示器没刷新过来,人肉眼也看不过来(有人可能又要抬杠说这是老板要求的,显示 ...
- C#中字符串格式化string.Forma中需要使用t转义字符的情况处理
此处汇总一下C#中字符串格式化string.Forma中需要使用t转义字符的情况处理. 1.C# string.Format() 方法中占位大括号的外面还有大括号,此时就需要使用转义大括号{}. 处理 ...
- Eclipse中如何快速查询一个类和方法在哪里被引用?
0.在Eclipse中使用全局搜索Ctrl+h的方法快速查询一个类在哪里被引用. 打开Eclipse,使用快捷键Ctrl+h,会弹出一个对话框,找到File Search页签,在Containing ...
- kubernetes系列(七) - Pod生命周期
目录 1. pod生命周期 2. initC 2.1 initC介绍 2.2 initC的作用 2.3 initC的模版 2.4 initC的一些其他补充 3. Pod健康性检查(liveiness) ...
- 单片机的主程序中为什么都要加一个while(1)?
*** * C51 为什么都要加一个while(1)?****** while(1)的作用: while(1) 是一个死循环 为了不让代码继续向下执行. 单片机中使用while(1),大部分:为了防止 ...