C++性能之戰(0)--Linux時間相關函數總結

0. 寫在最前面

本文持續更新地址:https://haoqchen.site/2019/12/17/linux-time-summary/

最近寫程序涉及到時間相關的,包括當前時間呀,進程運行的時間差呀,線程某段程序的時間消耗呀等等。然後查了比較多Linux下的時間函數。發現每個函數之間都有或多或少的區別,應用場景很不一樣。在此做個總結和記錄。如果沒時間細看可以直接跳到第3章的總結。

如無特殊說明,我的系統是Ubuntu1604(64bit)

對ROS的時間有興趣的可以看看我的另一篇文章https://haoqchen.site/2018/11/08/ROS-time/

如果覺得寫得還不錯,可以找我其他文章來看看哦~~~可以的話幫我github點個讚唄。
你的Star是作者堅持下去的最大動力哦~~~

1. 系統類函數

這裏所說的系統類函數主要是C標準庫中的函數。

1.1 gettimeofday

在終端下運行man gettimeofday可以看到其官方說明,我摘錄一些重點:

項目 說明 備註
頭文件 #include <sys/time.h>
原型 int gettimeofday(struct timeval *tv, struct timezone *tz);
功能 獲取從Epoch(1970年1月1日00:00:00 UTC,到2038年會掛那個)到當前所經過的時間(不考慮閏秒)以及當前時區,分辨率達us
return 成功返回0,失敗返回-1,可通過errno查看錯誤碼

其中struct timeval

struct timeval {
    time_t      tv_sec;     /* seconds */
    suseconds_t tv_usec;    /* microseconds */
};

其中time_tsuseconds_t是跟系統有關的類型,我在自己系統下看到的是long int

用法:

struct timeval t_start, t_end;
gettimeofday(&t_start, NULL);
do_something();
gettimeofday(&t_end, NULL);
long int time_cost = (t_start.tv_sec - t_end.tv_sec) * 1000 + (t_start.tv_usec - t_end.tv_usec)
printf("Cost time: %ld ms\n", time_cost);

1.2 times

項目 說明 備註
頭文件 #include <sys/times.h>
原型 clock_t times(struct tms *buf);
功能 返回當前進程的相關時間,包括用戶時間,系統時間,子進程用戶時間,子進程系統時間
return 失敗時返回-1,成功時返回過去某一時間點到現在經過的CPU計數,每秒的脈衝數用sysconf(_SC_CLK_TCK)獲取 這個值可能溢出

其中tms,單位都是CPU計數:

struct tms {
    clock_t tms_utime;  /* user time */
    clock_t tms_stime;  /* system time */
    clock_t tms_cutime; /* user time of children */
    clock_t tms_cstime; /* system time of children */
};

1.3 clock_gettime

項目 說明 備註
頭文件 #include <time.h>
原型 int clock_gettime(clockid_t clk_id, struct timespec *tp);
功能 獲取系統某一時鐘從Epoch到當前的時間,可精確到納秒
return 成功返回0,失敗返回-1

其中timespec

struct timespec {
    time_t   tv_sec;        /* seconds */
    long     tv_nsec;       /* nanoseconds */
};

可用的時鐘:

  • CLOCK_REALTIME:就是所說的wall-clock,會受非連續跳躍影響,比如人爲修改了時鐘。或者增量的調整,比如使用adjtime函數
  • CLOCK_REALTIME_COARSE:精簡版的CLOCK_REALTIME,很快,但精度受損。
  • CLOCK_MONOTONIC:單調的時鐘?從某個不確定的時刻開始跳動,不受非連續跳躍的影響,但是受增量的調整影響。
  • CLOCK_MONOTONIC_COARSE:精簡版的CLOCK_MONOTONIC,很快,但精度受損。
  • CLOCK_MONOTONIC_RAW:與CLOCK_MONOTONIC類似,但輸出的是原始的時鐘,不受其他調整影響
  • CLOCK_BOOTTIME:與CLOCK_MONOTONIC完全一致,但把系統暫停的時間也算在內
  • CLOCK_PROCESS_CPUTIME_ID:統計進程所有線程消耗的時間
  • CLOCK_THREAD_CPUTIME_ID:線程消耗的時間,這個ID是針對當前線程的,如果想獲得其他線程的ID,需要調用pthread_getcpuclockid

用法:

#include <time.h>
#include <string>
#include <sstream>

auto time2String = [](struct timespec t_start, struct timespec t_end) -> const std::string {
    std::string result;
    std::stringstream ss;
    long double temp = 0.0;
    temp += (t_end.tv_sec - t_start.tv_sec);
    temp += static_cast<long double>((t_end.tv_nsec - t_start.tv_nsec) / 1000u) / static_cast<long double>(1000000.0);
    ss.precision(6);
    ss.setf(std::ios::fixed);
    ss << temp  ;
    ss >> result;
    return result;
};

struct timespec time_start;
struct timespec time0;
time0.tv_sec = 0;
time0.tv_nsec = 0;
if (0 == clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time_start)){
    std::cout << "start time: " << time2String(time0, time_start) << "--------------" << std::endl;
}

struct timespec time_1;
if (0 == clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time_1)){
    std::cout << "1st time: " << time2String(time_start, time_1) << std::endl;
}

1.4 clock

同樣可從man中看到相關說明

項目 說明 備註
頭文件 #include <time.h>
原型 clock_t clock(void);
功能 返回程序所用的處理器時間近似,也就是目前爲止所用的CPU時間,是否包括sleep的時間與系統有關 在32位系統由於位數關係,約每72分鐘循環一次
return 返回CPU的時鐘計數,錯誤返回-1。clock_t在我的系統下是long int,要獲得時間,需要除以CLOCKS_PER_SEC

注意:

  • Linux中,返回的時間不包括wait子線程的時間,其他系統不確定。可通過times函數來獲得
  • 在glibc 2.17及之前的版本,clock是基於times來實現的,在後續版本,爲了提高精度,其基於clock_gettime實現。
  • 在C標準中,程序開始時clock的返回值可爲任意值,每個系統的實現會有不同,所以最好的方式是程序開始時獲取一個初始值,後面的時間減去這個初始值。
  • CLOCKS_PER_SEC在所有XSI-conformant系統中被定義爲1000000,也即時間分辨率爲1us

注:個人感覺這個函數沒啥用,精度不及其他,然後又諸多限制

2. C++標準類函數

2.1 time

項目 說明 備註
頭文件 #include
原型 std::time_t time( std::time_t *time );
功能 返回日曆時間,即從Epoch到當前所經過的秒數
return 如上,失敗時返回-1

一般情況下,我們需要的是時間描述,而不是一個乾巴巴的秒數,所以會調用struct tm * localtime (const time_t * timer);函數將其轉換成一個時間描述結構,其包括:

int tm_sec
int tm_min
int tm_hour
int tm_mday
int tm_mon
int tm_year
int tm_wday
int tm_yday
int tm_isdst

如上所述,時間精確到秒。

如果你想進一步轉換成字符串描述,可以調用char* asctime( const struct tm* time_ptr );進行轉換。

參考:

2.2 std::chrono時間庫

詳見chrono的CPPReference說明,這個庫主要提供三個時鐘的獲取和處理,這三個時鐘被封裝成類,並且都是通過返回當前的time_point時間節點來獲得時間。在chrono庫中,所有的時間節點都是相對於Epoch

時鐘類 特點
steady_clock 1. 設計用於計算時間間隔
2. 計數間隔是穩定的,間隔1ns(官方說明的代碼受double精度限制是us的)
3. 一般是系統啓動的時間,且保證後面的時間永遠不比前面得到的時間小
system_clock 1. 設計用於表示真實時間,即日曆時間,刻度爲1個tick,_XTIME_NSECS_PER_TICK納秒
2. 時間點可爲負數
3. 系統中所有進程用該時鐘,時間節點都是一樣的
4. 可與time_t相互轉換
high_resolution_clock 1. 該時鐘是系統中頻率最高的
2. 該時鐘有可能與上述兩個時鐘是一樣的

參考:

3. 使用建議

  • 自由度最高的是clock_gettime
  • 由於系統存在線程調度的問題,所以獲取日曆時鐘計算時間差都是有可能包含其他無關線程的執行時間的(一般約10ms)。要想只獲取當前線程的時間差,貌似只能用clock_gettime
  • 如果只關心自身進程,可使用clock_gettimeclocktimes,其中times還可以方便地統計哪些是系統消耗的,哪些是自身消耗的。
  • 如果是想方便地輸出字符串信息,可使用C++的time
  • 其實目前的晶振頻率都挺高的,無論用哪個時鐘,精度都能得到保證,更多地應該考慮系統調度的問題。

參考


喜歡我的文章的話Star一下唄Star

版權聲明:本文爲白夜行的狼原創文章,未經允許不得以任何形式轉載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章