這裡整理了 C/C++ 中各種測量時間的函數與用法,並提供完整的範例程式碼,讓程式開發者方便測量程式執行速度。

這裡我蒐集了一些在 C/C++ 中常見的程式執行速度測量方式,因為時間的量測方式與細節非常多,這裡只是簡單寫一些常用的方式與範例。

程式中的時間

在測量程式執行所花費的時間前,必須先認識一下時間的測量方式,不同的測量方法會得到不同的結果,其意義也不同。

Wall-Clock Time

Wall-clock time 顧名思義就是真實世界的時間,相當於以牆上的時鐘為依據所計算出來的時間,這個時間會牽涉到校時、時區以及夏令時間之類的問題,詳細說明請參考維基百科的 Wall-clock time 說明。

由於 wall-clock time 並不是單調遞增(monotonic)的數值,所以它不是一個穩定的時間依據,只能做為參考用,若需要非常精準的量測程式效能,不建議使用這種時間。

CPU Time

CPU time 是指程式真正使用 CPU 在執行的時間,而這個時間又可以細分為兩種:

  • user time:程式本身執行的時間(user space)。
  • system time:作業系統層級執行的時間(kernel space)。

詳細說明請參考維基百科的 CPU timeUser space 說明。

對於多執行緒(multithreading)的程式,其 CPU time 就是每條執行緒的執行時間總和,所以平行化的程式其 CPU time 可能會比 wall-clock time 還要長。

C 語言範例

這是一個利用蒙地卡羅演算法計算 pi 的範例:

#include <stdlib.h>
#include <stdio.h>
#include <math.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() {
  double result = pi(1e8);
  printf("PI = %f\n", result);
}

使用 gcc 編譯:

gcc -o pi pi.c

若使用比較舊的 gcc 編譯器,要加上 -std=c99 參數:

gcc -std=c99 -o pi pi.c

以下我們將以這個程式為例,介紹測量程式執行時間的方法。

Linux time 指令

在 Linux 中有一個 time 指令可以直接測試程式的執行時間(CPU time):

time ./pi
PI = 3.142172

real    0m2.210s
user    0m2.209s
sys     0m0.001s

time 指令的輸出分為 user time、system time 以及實際上所花費的時間。

如果在系統上同時有其他的程式也在使用 CPU 時,結果會有些差異。我先使用 stress 讓 CPU 滿載:

stress --cpu 40

接著再測試一次:

time ./pi
PI = 3.142172

real    0m3.706s
user    0m3.600s
sys     0m0.000s

time 函數

C 標準函式庫的 time 函數可以傳回系統上的 wall-clock time,精準度為 1 秒,以下是範例。

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h> // time 函數所需之標頭檔
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() {
  // 儲存時間用的變數
  time_t start, end;

  // 開始計算時間
  start = time(NULL);

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

  // 結束計算時間
  end = time(NULL);

  // 計算實際花費時間
  double diff = difftime(end, start);

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

gcc 編譯:

gcc -o pi pi.c

執行:

./pi
PI = 3.142172
Time = 2.000000

因為 time 函數精準度只有 1 秒,所以這個測量方式不太適合太小的程式。

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

./pi
PI = 3.142172
Time = 4.000000

clock 函數

標準的 C 函式庫中,有一個 clock 函數可以傳回程式的 CPU 時脈數(clock ticks),可計算 CPU time,使用範例如下:

#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <time.h> // clock 函數所需之標頭檔
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() {
  // 儲存時間用的變數
  clock_t start, end;
  double cpu_time_used;

  // 計算開始時間
  start = clock();
  
  // 主要計算
  double result = pi(1e8);
    
  // 計算結束時間
  end = clock();
  
  // 計算實際花費時間
  cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;

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

clock 函式所傳回的數值是 CPU 的時脈數,並不是真正的時間,而 CLOCKS_PER_SEC 這個巨集(macro)是每秒 CPU 的時脈數,相除之後就是程式所使用的 CPU time,單位為秒,這個時間包含 user time 與 system time。

gcc 編譯:

gcc -o pi pi.c

執行:

./pi
PI = 3.142172
Time = 2.250000

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

./pi
PI = 3.142172
Time = 3.670000