clock_gettime 函數

clock_gettime 函數可以取得 wall-clock time 或程式的 CPU time,其所傳回的時間是用 timespec 這個結構(struct)所儲存的:

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

使用 timespec 來儲存時間的話,其精準度最高可達十億分之一秒(nanosecond),若要查詢實際的精確度,可以使用 clock_getres 函數:

#include <time.h>
#include <stdio.h>
int main() {
  struct timespec t;
  clock_getres(CLOCK_MONOTONIC, &t);
  printf("Resolution: %ld nanosecond\n", t.tv_nsec);
  return 0;
}
gcc -o getres getres.c
./getres
Resolution: 1 nanosecond

clock_getres 的第一個參數是指定時間的類型,常見的類型有:

  • CLOCK_REALTIME:系統的實際時間(wall-clock time)。
  • CLOCK_REALTIME_COARSE:系統的實際時間(wall-clock time),取得速度快,但精確度校低。
  • CLOCK_MONOTONIC:單調遞增時間(monotonic time),這個時間會非常穩定的持續遞增,不會因為系統時間改變而有變動,適合用於測量程式執行效能。
  • CLOCK_MONOTONIC_COARSE:與 CLOCK_MONOTONIC 類似,取得速度快,但精確度校低。
  • CLOCK_MONOTONIC_RAW:與 CLOCK_MONOTONIC 類似,但是它是從硬體時鐘所讀取出來的值。
  • CLOCK_PROCESS_CPUTIME_ID:程式行程的 CPU time,這個時間包含所有的執行序所花費的時間。
  • CLOCK_THREAD_CPUTIME_ID:程式單一執行序所耗費的時間。
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h> // clock_gettime 函數所需之標頭檔
double pi(int n) {
  srand(5);
  int count = 0;
  double x, y;
  for (int i = 0; i < n; ++i) {
    x = (double) rand() / RAND_MAX;
    y = (double) rand() / RAND_MAX;
    if (x * x + y * y <= 1) ++count;
  }
  return (double) count / n * 4;
}
struct timespec diff(struct timespec start, struct timespec end) {
  struct timespec temp;
  if ((end.tv_nsec-start.tv_nsec)<0) {
    temp.tv_sec = end.tv_sec-start.tv_sec-1;
    temp.tv_nsec = 1000000000+end.tv_nsec-start.tv_nsec;
  } else {
    temp.tv_sec = end.tv_sec-start.tv_sec;
    temp.tv_nsec = end.tv_nsec-start.tv_nsec;
  }
  return temp;
}
int main() {
  // 儲存時間用的變數
  struct timespec start, end;
  double time_used;

  // 計算開始時間
  clock_gettime(CLOCK_MONOTONIC, &start);

  // 主要計算
  double result = pi(1e8);

  // 計算結束時間
  clock_gettime(CLOCK_MONOTONIC, &end);

  // 計算實際花費時間
  struct timespec temp = diff(start, end);
  time_used = temp.tv_sec + (double) temp.tv_nsec / 1000000000.0;

  printf("PI = %f\n", result);
  printf("Time = %f\n", time_used);
}

gcc 編譯:

gcc -o pi pi.c

執行:

./pi
PI = 3.142172
Time = 2.145160

在 CPU 滿載(使用 stress)的狀況下,測試的結果:

./pi
PI = 3.142172
Time = 3.773047

getrusage 函數

getrusage 函數可以取得程式所使用的各種系統資源統計數據,包含 CPU、記憶體、I/O 等,所以我們也可以利用這個函數來測量程式的 CPU time:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <stdint.h>
#include <sys/time.h>
#include <sys/resource.h>
double pi(int n) {
  srand(5);
  int count = 0;
  double x, y;
  for (int i = 0; i < n; ++i) {
    x = (double) rand() / RAND_MAX;
    y = (double) rand() / RAND_MAX;
    if (x * x + y * y <= 1) ++count;
  }
  return (double) count / n * 4;
}
int main(int argc, char *argv[]) {
  struct rusage ru;
  struct timeval utime;
  struct timeval stime;

  // 主要計算
  double result = pi(1e8);

  // 取得程式的 user time 與 system time
  getrusage(RUSAGE_SELF, &ru);

  printf("PI = %f\n", result);
  utime = ru.ru_utime;
  stime = ru.ru_stime;
  double utime_used = utime.tv_sec + (double) utime.tv_usec / 1000000.0;
  double stime_used = stime.tv_sec + (double) stime.tv_usec / 1000000.0;
  printf("User Time = %f\n", utime_used);
  printf("System Time = %f\n", stime_used);

  return 0;
}

getrusage 函數可以分別取得程式的 user CPU time 與 system CPU time,有類似 time 指令的效果。

gcc 編譯:

gcc -o pi pi.c

執行:

./pi
PI = 3.142172
User Time = 2.258956
System Time = 0.000999

在 CPU 滿載(使用 stress)的狀況下,測試的結果:

./pi
PI = 3.142172
User Time = 3.670714
System Time = 0.001000

gettimeofday 函數

gettimeofday 函數可以取得很精確的 wall-clock time。

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <sys/time.h>
double pi(int n) {
  srand(5);
  int count = 0;
  double x, y;
  for (int i = 0; i < n; ++i) {
    x = (double) rand() / RAND_MAX;
    y = (double) rand() / RAND_MAX;
    if (x * x + y * y <= 1) ++count;
  }
  return (double) count / n * 4;
}
int main(int argc, char *argv[]) {
  struct timeval start, end, diff;

  // 開始計算時間
  gettimeofday(&start, NULL);

  // 主要計算
  double result = pi(1e8);

  // 結束計算時間
  gettimeofday(&end, NULL);

  // 計算實際花費時間
  timersub(&end, &start, &diff);

  double time_used = diff.tv_sec + (double) diff.tv_usec / 1000000.0;
  printf("PI = %f\n", result);
  printf("Time = %f\n", time_used);

  return 0;
}

gcc 編譯:

gcc -o pi pi.c

執行:

./pi
PI = 3.142172
Time = 2.258592

在 CPU 滿載(使用 stress)的狀況下,測試的結果:

./pi
PI = 3.142172
Time = 3.760840

後記

我原本想要找一個比較穩定的測試方式,但是透過 stress 的測試結果來看,幾乎每一種方式都會受到系統負載的影響,若未來有看到比較好的方式,再補上來。

參考資料:StackOverflowcharles620016Guy Rutenberg程式扎記Edison.X. BlogCodingUnit