目录
1.4 C++ Windows API GetTickCount()
1.5 C++ Windows API QueryPerformanceCounter() 【高精度】
1.6 C++ linux环境 gettimeofday() 【高精度】
【内容繁杂,水平有限,必有差错,如若发现,感谢指正!】
如何准确计算程序在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::nanoseconds | duration</*至少 64 位的有符号整数类型*/, std::nano> |
std::chrono::microseconds | duration</*至少 55 位的有符号整数类型*/, std::micro> |
std::chrono::milliseconds | duration</*至少 45 位的有符号整数类型*/, std::milli> |
std::chrono::seconds | duration</*至少 35 位的有符号整数类型*/> |
std::chrono::minutes | duration</*至少 29 位的有符号整数类型*/, std::ratio<60>> |
std::chrono::hours | duration</*至少 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)
其中,方法二的精度比较高。方法一基本上是性能最差的。这个其实是和系统有关系的。一般我们推荐使用方法二和方法三。