【C/C++/CUDA C/Python】CPU/GPU 程序块运行时间多种计算方法全解析

目录

1. CPU程序块运行时间计算

1.1 C++ clock()函数

1.2 C++ chrono库

1.3 C++ time()函数【低精度(秒级)】

1.4 C++ Windows API GetTickCount()

1.5 C++ Windows API QueryPerformanceCounter() 【高精度】

1.6 C++ linux环境 gettimeofday()  【高精度】

2. GPU程序块运行时间计算

2.1 CUDA计时事件 cudaEvent_t

3. CPU计时方法用于GPU任务计时

4. python程序块运行时间计算

4.1 datetime.datetime.now()

4.2 time.time()

4.3 time.clock()


【内容繁杂,水平有限,必有差错,如若发现,感谢指正!】 


如何准确计算程序在CPU上或者GPU上执行的时间呢?如果使用CPU或者操作系统中的某个计时器,将带来某种延迟(包括操作系统线程调度,高精度CPU计时器可用性等方面)。而且,当GPU核函数运行时,还可以在主机上异步地执行计算。但如果仅测量这些程序在CPU上运算时间我们可以使用CPU或者操作系统的定时机制。而测量GPU在某个任务上花费的时间,则可以使用CUDA的事件API。

1. CPU程序块运行时间计算

1.1 C++ clock()函数

C系统调用方法,需要头文件 ctime/time.h,即Windows和Linux都可以使用。

  • clock()返回类型为clock_t类型
  • clock_t实际上为long类型, typedef long clock_t
  • clock()函数,返回从 开启这个程序进程 到 程序中调用clock()函数 之间的CPU时钟计时单元(clock tick)数,返回单位为毫秒
  • 可以使用常量CLOCKS_PER_SEC,这个常量表示每一秒有多少时钟计时单元

示例代码:

#include <time.h>   //引入头文件
int main()
{
clock_t start,end;   //定义clock_t变量
start = clock();     //开始时间

fun()  //需计时的函数

end = clock();   //结束时间
cout<<"time = "<<double(end-start)/CLOCKS_PER_SEC<<"s"<<endl;  //输出时间(单位:s)
}

1.2 C++ chrono库

chrono是C++ 11引入的一个精确中立的时间和日期库,要使用chrono要在添加chrono库。std::chrono 主要包含两部分,std::chrono::duration 和 std::chrono::time_point,它们都是类模板,std::chrono::duration 表示时间间隔,std::chrono::time_point 表示时间戳。

duration:

template <class Rep, class Period = ratio<1> > class duration;

其中Rep表示一种数值类型,用来表示Period的数量,比如int float double;Period是ratio类型,用来表示用秒表示的时间单位,比如second milisecond。

template <std::intmax_t Num, std::intmax_t Denom = 1> class ratio;

ratio 表示值等于 Num/Denom 的有理数,它的静态数据成员 num 与 den 表示由 Num 与 Denom 除以其最大公约数的分子与分母。

duration 的常用类型定义:

类型定义
std::chrono::nanosecondsduration</*至少 64 位的有符号整数类型*/, std::nano>
std::chrono::microsecondsduration</*至少 55 位的有符号整数类型*/, std::micro>
std::chrono::millisecondsduration</*至少 45 位的有符号整数类型*/, std::milli>
std::chrono::secondsduration</*至少 35 位的有符号整数类型*/>
std::chrono::minutesduration</*至少 29 位的有符号整数类型*/, std::ratio<60>>
std::chrono::hoursduration</*至少 23 位的有符号整数类型*/, std::ratio<3600>>

time_point:

template <class Clock, class Duration = typename Clock::duration> class time_point;

 time_point 被实现为存储一个 duration 类型的自 Clock 的纪元起始开始的时间间隔的值,可以简单理解为。Clock 必须满足时钟(Clock)的要求,库提供了三种 Clock:

描述
system_clock来自系统范畴实时时钟的挂钟时间
steady_clock决不会调整的单调时钟
high_resolution_clock拥有可用的最短嘀嗒周期的时钟

代码示例: 

#include <iostream>
#include <chrono>

int main ()
{
    using namespace std::chrono;
    steady_clock::time_point t1 = steady_clock::now();
 
    for (int i=0; i<1000; ++i) std::cout << "*"; //测试代码块

    steady_clock::time_point t2 = steady_clock::now();
    duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
    std::cout << "Time used: " << time_span.count() << " seconds.";
    std::cout << std::endl;

    return 0;
}

1.3 C++ time()函数【低精度(秒级)】

time()为C系统调用方法,但是精度很低(秒级),不建议使用。

代码示例:

time_t start,stop;
start = time(NULL);
fun();
stop = time(NULL);

1.4 C++ Windows API GetTickCount()

GetTickCount()是一个Windows API,所需头文件为<windows.h>。返回从操作系统启动到现在所经过的毫秒数(ms),精确度有限,跟CPU有关,一般精确度在16ms左右,最精确也不会精确过10ms。它的返回值是DWORD,当统计的毫妙数过大时,将会使结果归0,影响统计结果。

示例代码:

#include <windows.h>   //引入头文件
int main()
{
    DWORD t1,t2;
    t1 = GetTickCount();

    fun()  //需计时的函数

    t2 = GetTickCount();
    cout<<"time = "<<((t2-t1)<<endl;  //输出时间(单位:ms)
}

1.5 C++ Windows API QueryPerformanceCounter() 【高精度】

QueryPerformanceCounter()是一个Windows API,所需头文件为<windows.h>。这个函数返回高精确度性能计数器的值,它可以以微妙为单位计时。但是QueryPerformanceCounter() 确切的精确计时的最小单位是与系统有关的,所以,必须要查询系统以得到QueryPerformanceCounter()返回的嘀哒声的频率。 QueryPerformanceFrequency() 提供了这个频率值,返回每秒嘀哒声的个数。

示例代码:

#include <windows.h>   //引入头文件
int main()
{
    LARGE_INTEGER t1,t2,tc;
    QueryPerformanceFrequency(&tc);
    QueryPerformanceCounter(&t1);

    fun()  //需计时的函数

    QueryPerformanceCounter(&t2);
    time=(double)(t2.QuadPart-t1.QuadPart)/(double)tc.QuadPart; 
    cout<<"time = "<<time<<endl;  //输出时间(单位:s)
}

1.6 C++ linux环境 gettimeofday()  【高精度】

gettimeofday() linux环境下的计时函数,int gettimeofday ( struct timeval * tv , struct timezone * tz ),gettimeofday()会把目前的时间由tv所指的结构返回,当地时区的信息则放到tz所指的结构中。

//timeval结构定义为:
struct timeval{
long tv_sec; /*秒*/
long tv_usec; /*微秒*/
};

//timezone 结构定义为:
struct timezone{
int tz_minuteswest; /*和Greenwich 时间差了多少分钟*/
int tz_dsttime; /*日光节约时间的状态*/
};

 这个函数获取从1970年1月1日到现在经过的时间和时区(UTC时间),(按照linux的官方文档,时区已经不再使用,正常应该传NULL)。

代码示例:

#include <sys/time.h>   //引入头文件
int main()
{
    struct timeval t1,t2;
    double timeuse;
    gettimeofday(&t1,NULL);

    fun();

    gettimeofday(&t2,NULL);
    timeuse = (t2.tv_sec - t1.tv_sec) + (double)(t2.tv_usec - t1.tv_usec)/1000000.0;

    cout<<"time = "<<timeuse<<endl;  //输出时间(单位:s)
}

2. GPU程序块运行时间计算

GPU与CPU任务执行具有异步性的特点,因此要考虑到这一特点,正确使用相应方法进行GPU任务时间计算。其中最常用也是最简单的就是CUDA提供的事件API。

2.1 CUDA计时事件 cudaEvent_t

CUDA中的事件本质上是一个GPU时间戳,这个时间戳是在用户指定的时间点上记录的。由于GPU本身支持记录时间戳,因此就避免了使用CPU定时器来统计GPU执行事件可能遇到的诸多问题。这个API使用起来也很容易。获得一个时间戳只需要两步:首先创建一个事件,然后记录这个事件:

cudaEvent_t start;
cudaEventCreate(&start);
cudaEventRecord(start, 0);

可以看到,当告诉运行时(runtime)记录事件start时还要指定第二个参数。在前面的示例这个参数为0.这个参数与流(Stream)相关。

要统计一段代码的执行事件,不仅要创建一个起始事件,还要创建一个结束事件。当在GPU上执行某个工作时,我们不仅要告诉CUDA运行时记录起始时间,还要记录结束时间:

cudaEvent_t start,stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start, 0);

//在GPU上执行一些任务

cudaEventRecord(stop, 0);

 然而,当按照这种方式来统计GPU代码的执行时间时,仍然存在一个问题。要修复这个问题,只需一行代码,但需要对这行代码进行一些解释。在使用事件时,最复杂的情况是我们在CUDA C中执行某些异步函数的调用。例如,但启动光线追踪的核函数时,GPU开始执行之前,CPU会继续执行程序中的下一行代码。从性能的角度来看,这是非常好的,但这意味着我们在GPU和CPU上同时进行计算,但从逻辑概念上来看,这将使计时工作变得更加复杂。

你应该将cudaEventRecord()视为一条记录当前时间的语句,并且把这条语句放入GPU的未完成工作队列中。因此,直到GPU执行完了在调用cudaEventRecord()之前的所有语句时,时间才会被记录下里。由事件stop事件来测量正确的时间正是我们所希望的,但仅当GPU完成了之前的工作并且记录了stop事件后,才能安全地读取stop时间值。幸运的是,我们有一种方式告诉CPU在某个时间上同步,这个事件API函数就是cudaEventSynchronize():

cudaEvent_t start,stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start, 0);

//在GPU上执行一些任务

cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);

现在,我们已经告诉运行时阻塞后面的语句,直到GPU执行到达stop事件。当cudaEventSynchronize返回时,我们知道在stop事件之前的所有GPU工作已经完成了,因此可以安全地读取在stop中保存的时间戳。值得注意的是,由于CUDA事件是直接在GPU上实现的,因此它们不适用于对同时包含设备(GPU)代码和主机(CPU)代码的混合代码计时。 也就是说,如果你试图通过CUDA事件对核函数和设备(GPU)内存赋值之外的代码进行计时,将得到不可靠的结果。

完整示例代码:

//记录起始时间 
cudaEvent_t start,stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start, 0);

//在GPU上执行一些任务

cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);

float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
print("Time cost: %3.1f ms", elapsedTime);
cudaEventDestroy(start);
cudaEventDestroy(stop);

cudaEventElpasedTime()是一个工具函数,用来计算两个时间之间经历的时间。第一个参数为某个浮点 变量的地址,在这个参数中将包含两次事件之间经历的时间,单位为毫秒。

当使用完事件后,需要调用cudaEventDestory()来销毁它们。这相当于对malloc()分配的内存调用free(),因此每个cudaEventCreate()都对应一个cudaEventDestroy()同样是非常重要的。

3. CPU计时方法用于GPU任务计时

因为调用CUDA kernel 是非阻塞的,kernel语句后面的语句不等待kernel函数执行完,就会立即执行,所以直接使用CPU程序时间测量方法会造成测量失败。但是我们可以使用cudaThreadSynchronize() 暂停调用者的执行,直到前面的kernel函数执行完成。

经过cudaThreadSynchronize() 进行同步,前面所提及的所有CPU计时方法都可以用于GPU任务计时。

示例代码:

clock_start = clock();

//执行GPU任务

cudaThreadSynchronize();
clock_end= clock();
double clock_diff_sec = ((double)(clock_end- clock_start) / CLOCKS_PER_SEC);
printf("clock_ time: %lfms.\n", clock_diff_sec * 1000);

4. python程序块运行时间计算

4.1 datetime.datetime.now()

import datetime
start = datetime.datetime.now()
run_function():
    # do something
end = datetime.datetime.now()
print (end-start)

4.2 time.time()

import time
start = time.time()
run_function()
end = time.time()
print str(end-start)

4.3 time.clock()

import time
start = time.clock()
run_function()
end = time.clock()
print str(end-start)

其中,方法二的精度比较高。方法一基本上是性能最差的。这个其实是和系统有关系的。一般我们推荐使用方法二和方法三

  • 5
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值