Windows上测试C代码段运行时间方法

1、 测试方法分类

1.1概述

1.1.1 常见方法

目前查到的方法有:

  1. time()
  2. clock()
  3. rdtsc
  4. QueryPerformanceFrequency()
  5. timeGetTime()
  6. GetTickCount()
  7. gettimeofday
方法类型总结
1time()time.hC标准库,可移植性好、性能稳定,精度低(S)
2clock()time.h计算的是耗用多少个CPU的时钟单元,精确毫秒
3timeGetTime()WIN32API精度五毫秒或更大一些,这取决于机器的性能
4GetTickCount()WIN32API满足一般的实时控制,精确毫秒
5gettimeofday()Linux C函数在arm+linux的环境下此函数仍可使用,精度可达微秒
6RDTSC - 读取时间标签计数器X86架构CPU汇编指令缺点是非常短时间的计时会不稳定,精度ns
7QueryPerformanceFrequency()Win32API高精度定时,精度微妙

  考虑到移植性与精度,使用gettimeofday()

  clock函数计算的是耗用多少个CPU的时钟单元,使用sleep()并不占用cpu资源,所以clock不会计算在内。一般只可以用于单线程的情况,否则使用clock函数得到的时间偏大。time 与 gettimeofday两个函数得到的都是从Epoch开始到当前的秒数(tt=tv.tv_sec),而gettimeofday 还能得到更精细的微秒级结果,即tv_sec*(10^6)+tv_usec为从Epoch开始到当前的微秒数。time 和gettimeofday返回绝对时间,不受程序执行的影响,clock返回的是相对时间,是程序执行的时间,程序执行时间受到进程调度、阻塞等影响,而绝对时间不会。所以,当需要统计包含子程序在内的时间时,还是需要以绝对时间相减的形式计算,而不能使用clock简单统计,clock只能统计父进程的执行时间。[14][15][16][17]。

1.1.2 time.h

1) 概念
1:Coordinated Universal Time(UTC):
  协调世界时,又称世界标准时间,也即格林威治标准时间(Greenwich Mean Time,GMT),中国内地的时间与UTC得时差为+8,也即UTC+8,美国为UTC-5。

2:Calendar Time:
  日历时间,是用“从一个标准时间点到此时的时间经过的秒数”来表示的时间。标准时间点对不同编译器可能会不同,但对一个编译系统来说,标准时间是不变的。一般是表示距离UTC时间 1970-01-01 00:00:00的秒数。
3:epoch:
  时间点。在标准c/c++中是一个整数,用此时的时间和标准时间点相差的秒数(即日历时间)来表示。
4:clock tick:
  时钟计时单元(而不叫做时钟滴答次数),一个时钟计时单元的时间长短是由cpu控制的,一个clock tick不是cpu的一个时钟周期,而是c/c++的一个基本计时单位。

Sleep函数:
  功 能: 执行挂起一段时间
  用 法: unsigned sleep(unsigned seconds);

注意:
  在VC中使用带上头文件#include <windows.h>,在Linux下,gcc编译器中,使用的头文件因gcc版本的不同而不同#include <unistd.h>
  在VC中,Sleep中的第一个英文字符为大写的"S" ,在linux下不要大写,在标准C中是sleep, 不要大写,简单的说VC用Sleep, 别的一律使用sleep
  在VC中,Sleep()里面的单位,是以毫秒为单位,所以如果想让函数滞留1秒的话,应该是Sleep(1000); 在Linux下,sleep()里面的单位是秒,而不是毫秒。

2) 变量解释
  time.h 头文件定义了四个变量类型、两个宏和各种操作日期和时间的函数[1]。

4个变量

类型描述
size_t是无符号整数类型,它是 sizeof 关键字的结果。
clock_t这是一个适合存储处理器时间的类型,类型为unsigned long
time_t这是一个适合存储日历时间类型。
struct tm这是一个用来保存时间和日期的结构。

tm 结构的定义如下:

struct tm 
{
   int tm_sec;         /* 秒,范围从 0 到 59      */
   int tm_min;         /* 分,范围从 0 到 59      */
   int tm_hour;        /* 小时,范围从 0 到 23     */
   int tm_mday;        /* 一月中的第几天,范围从 1 到 31    */
   int tm_mon;         /* 月,范围从 0 到 11(注意)  */
   int tm_year;        /* 自 1900 年起的年数      */
   int tm_wday;        /* 一周中的第几天,范围从 0 到 6 */
   int tm_yday;        /* 一年中的第几天,范围从 0 到 365   */
   int tm_isdst;       /* 夏令时               */
};

两个宏

类型描述
NULL这个宏是一个空指针常量的值。
CLOCKS_PER_SEC这个宏表示每秒的处理器时钟个数。用于将clock()函数的结果转化为以秒为单位的量,这个量的具体值是与操作系统相关的,通常为1000。

1.2 具体实现

1.2.1 time()

精度:秒

/*
函数原型: time_t time(time_t *timer)
参数说明: timer=NULL时得到当前日历时间(从1970-01-01 00:00:00
            到现在的秒数),timer=时间数值时,用于设置日历时间,
            time_t是一个unsigned long类型。如果 timer不为空,
            则返回值也存储在变量 timer中。
函数功能: 得到当前日历时间或者设置日历时间
函数返回: 当前日历时间
*/
#include <stdio.h>
#include <time.h>

int main(){
    time_t start,end;
    start = time(NULL);

    /*...
        需要计时的代码
    ...*/

    end = time(NULL);
    printf("time = %ds\n", difftime(end, start));
    return 0;
}

参考[1][2][3]

1.2.2 clock()

  函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数。clock_t其实就是long,即长整形。该函数返回值是硬件滴答数,要换算成秒或者毫秒,需要除以CLK_TCK或者CLOCKS_PER_SEC。注意:本函数仅能返回ms级的计时精度(事实上能够达到的计时精度大致与操作系统的线程切换时间相当,在windows平台上,极限精度大致是15~16ms)。如果需要us级别的计时精度,Linux系统可以使用库函数:gettimeofday()[4][9]。

#include<stdio.h>
#include<stdlib.h>
#include<time.h>

int main(){
     clock_t start, end;
     start = clock();

     /*...
        需要计时的代码
    ...*/

     end = clock();
     printf("time=%f\n", (double)(end - start) / CLOCKS_PER_SEC);
     return 0;
}

注意:
CLOCKS_PER_SEC的值不是每秒的实际时钟滴答数,clock()是指挂钟而不是CPU时钟,准确度通常会略低[5]。

1.2.3 timeGetTime()

  函数以毫秒计的系统时间。该时间为从系统开启算起所经过的时间。

1.2.4 GetTickCount()

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include <string.h>
#include<math.h>
#include <time.h>
#include <pthread.h>
#include <windows.h>//使用Sleep的头



int main(void)
{
    double t1 = (double)GetTickCount();
    /******************待测代码********************/

    Sleep(1000);

    /**********************************************/
    double t2 = (double)GetTickCount();

   // printf("\ntime in %lf ms\n",(t2-t1)*1000/(cvGetTickFrequency()));
   // printf("\ntime in %lf ms\n",(t2-t1));
   printf("%lf,%lf\n",t1,t2);
   printf("\ntime in %lf ms\n",(t2-t1));
    return 0;
}

代码参考:

1.2.5 gettimeofday

#include <time.h>
#include <stdio.h>
#include<sys/time.h>

#define NEW_TIME_VALE
    struct timeval startTime,endTime;  \
    float Timeuse;\

#define START_GETTIME \
    gettimeofday(&startTime,NULL);  \

#define END_GETTIME \
    gettimeofday(&endTime,NULL);  \
    Timeuse = 1000000*(endTime.tv_sec - startTime.tv_sec) + (endTime.tv_usec - startTime.tv_usec);  \
    Timeuse /= 1000000;  \
    printf("Timeuse = %f\n",Timeuse);\

int main(void)
{
    NEW_TIME_VALE
    START_GETTIME

/******************待测时间代码*********************/
    Sleep(1000);
/**************************************************/

    END_GETTIME
    return 0;
}

代码参考:[11]

1.2.6 rdtsc

  Linux提供的API—gettimeofday()可以获取微秒级的精度。但是,首先它不能提供纳秒级精度,其次,他是一个库函数(可能不是系统调用),自身就有一定的开销,当我需要纳秒级精度时,误差会很大。而且,测定函数的性能以时钟周期为单位比纳秒更加合理。当不同型号的CPU的频率不同时,运行时间可能差很多,但是时钟周期应该差不了多少(如果指令集一样的话)。

  x86处理器为我们提供了rdtsc指令。从pentium开始,很多x86处理器都引入了TSC(Time Stamp Counter),一个64位的寄存器,每个CPU时钟周期其值加一。它记录了CPU从上电开始总共经历的时钟周期数。一个2.5GHz的CPU如果全速运行的话,那么其TSC就会在一秒内增加2,500,000,000(1GHz = 10^9 Hz)。使用rdtsc获得时间会比gettimeofday的方式开销小。

  rdtsc指令把TSC的低32位存放在EAX寄存器中,把TSC的高32位存放在EDX寄存器中。该指令可以在用户态执行。可以使用GCC内嵌汇编实现在用户态获取时钟周期[6]。

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include <string.h>
#include<math.h>
#include <time.h>
#include <pthread.h>
#include <windows.h>//使用Sleep的头

typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long int uint64_t;
uint64_t current_cycles(void)
{
    uint32_t low, high;
    asm volatile("rdtsc" : "=a"(low), "=d"(high));
    return ((uint64_t)low) | ((uint64_t)high << 32);
}
uint64_t current_system_frequency(void)
{
    uint64_t tick,tick1,time;

    tick = current_cycles();

    Sleep(1000);

    tick1 = current_cycles();

    time = (unsigned)((tick1-tick));
    return time;
}

int main(void)
{
    uint64_t sys_fre=0;
    uint64_t tick,tick1;
    double time;

    sys_fre = current_system_frequency();   //获得系统主频
    tick = current_cycles();
    /******************待测代码********************/

    Sleep(1000);

    /**********************************************/
    tick1 = current_cycles();

    time =((double)(tick1-tick)/sys_fre);

    printf("\ntime in %lf\n",time);
    return 0;

}

  但是RDTSC 指令测试指令周期和时间也存在一些问题[7]。

   1. pu的是频率是可以变化的,频率变化之后,tick是不准确的,要怎么办?
   2. 不同核心的tick是同步的么,如果不同步怎么办?
   3. cpu执行的指令有可能是乱序的,可能是测量的不准确

  对于问题1,2,可以查看cpu是否支持const tsc。支持这个特性的cpu,它的tsc是按照其标称频率流逝的,与CPU的实际工作频率与状态无关。
  对于问题3,除了 TSC 要准,还要顾及 CPU 的乱序执行,尤其是如果被测的代码段较小,乱序执行可能让你的测试变得完全没意义。解决这个一般是“先同步,后计时”,在 wikipedia 上的一段代码就用 cpuid 指令来同步 (http://en.wikipedia.org/wiki/Time_Stamp_Counter)。

1.2.7 QueryPerformanceFrequency()

  在Windows平台下,常用的计时器有两种,一种是timeGetTime多媒体计时器,它可以提供毫秒级的计时。但这个精度对很多应用场合而言还是太粗糙了。另一种就是QueryPerformanceCount计数器,随系统的不同可以提供微秒级的计数。

  在多核心或多处理器的计算机上,特别是在支持CPU频率动态调整的计算机上,windows系统下的QueryPerformanceFrequency()获取HPET (如果存在)的频率,而QueryPerformanceCounter() 获取HPET(如果存在高精度事件定时器(High Precision Event Timer))自上电以来时钟周期数,与CPU频率无关 。前一个函数 不会因为线程运行所在的CPU不同或CPU的频率在不同时刻的差异而反馈不同的值,系统上电初始化以后便不会改变 ,后一个函数的源也是统一的。这样便可以精确计算目标程序,特别是多线程程序的性能。

  如果想获得CPU的默认信息,包括制造商,版本号,默认频率等等,请使用:__cpuid()。如果想获得当前CPU的实际频率,请获取系统当前基频和倍频然后计算得到。

  注:但是这和程序具体的运行环境有关,程序运行的流畅顺利就所用的时间就短,程序运行的环境不好,持续时间久很长。虽然可以实现高精度定时计数,但是程序的执行时间还是和运行环境有关,无法做到精确测量[10]。

#include<stdio.h>
#include <stdlib.h>
#include<time.h>
#include <windows.h>


int main() {

    double run_time;
    LARGE_INTEGER time_start;   //开始时间
    LARGE_INTEGER time_over;    //结束时间
    double dqFreq;      //计时器频率
    LARGE_INTEGER f;    //计时器频率
    QueryPerformanceFrequency(&f);
    dqFreq=(double)f.QuadPart;
    QueryPerformanceCounter(&time_start);   //计时开始
/******************待测时间代码*********************/
    Sleep(1000);
/**************************************************/


    QueryPerformanceCounter(&time_over);    //计时结束
    run_time=1000000*(time_over.QuadPart-time_start.QuadPart)/dqFreq;
    //乘以1000000把单位由秒化为微秒,精度为1000 000/(cpu主频)微秒
    printf("\nrun_time:%fus\n",run_time);
    printf("\nrun_time:%fms\n",run_time/1000);
    printf("\nrun_time:%fs\n",run_time/1000/1000);
    return 0;
}

代码参考:[8]

参考链接

[1] C语言中的time函数总结:https://blog.youkuaiyun.com/hmxz2nn/article/details/77986450
[2] C 库函数 - time():https://www.runoob.com/cprogramming/c-function-time.html
[3] C语言time()函数的用法: https://blog.youkuaiyun.com/jaylee123123/article/details/80986913
[4] clock()函数的返回值精度问题: https://blog.youkuaiyun.com/Fire_to_cheat_/article/details/78265264
[5] clock()精度: https://www.it1352.com/1999135.html
[6] 使用rdtsc测量函数的执行时间: https://www.jianshu.com/p/0c519fc6248b
[7] 多核时代不宜再用 x86 的 RDTSC 指令测试指令周期和时间: https://blog.youkuaiyun.com/Solstice/article/details/5196544
[8] C语言 计算程序运行时间(精确到毫秒/微秒):https://blog.youkuaiyun.com/Bamboo_shui/article/details/78757281
[9] C语言程序执行时间计时方法汇总:https://www.cnblogs.com/-yhwu/p/14970736.html
[10] QueryPerformanceFrequency用法–Windows高精度定时计数: https://blog.youkuaiyun.com/duiwangxiaomi/article/details/103895384
[11] 使用gettimeofday测试函数运行的时间: https://www.cnblogs.com/mylinux/p/5295959.html
[12] 【c++】函数time和clock的计时区别: https://blog.youkuaiyun.com/qq525003138/article/details/107095324/
[13] time()函数和clock()函数的区别: https://blog.youkuaiyun.com/weixin_44218238/article/details/124122738
[14] C/C++获取时间方法:gettimeofday():https://blog.youkuaiyun.com/wu694128/article/details/94542858
[15] 函数time()与gettimeofday()的区别: https://blog.youkuaiyun.com/shanzhizi/article/details/7708820
[16] Linux 时间函数 (time() / clock() / gettimeofday()) 简介与比较: https://www.youkuaiyun.com/tags/MtjakgwsMzEzODktYmxvZwO0O0OO0O0O.html
[17] C语言中常用计时方法总结: https://www.bbsmax.com/A/xl56KrR1dr/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值