程序調優中如何穩定地測試程序運行時間

程序調優中如何穩定地測試程序運行時間

計算代碼段的運行時間

關於程序運行過程中,有效地測試程序運行時間有下面幾個方法。

使用clock()函數進行程序運行時間統計

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

int main(int argc, char** argv) {
    clock_t start_cnt, end_cnt;
    start_cnt = clock();
    // ...
    end_cnt = clock();
    double t = (end_cnt - start_cnt) / CLOCKS_PER_SEC;
    printf("Use time: %f s. \n", t); // 單位爲秒
    return 0;
}

CLOCKS_PER_SEC爲每秒的計時週期數,一般CLOCKS_PER_SEC的值爲1000000,即以us爲單位。

使用rdtsc彙編指令計算實際運行CPU週期數

這種方法雖然精度比較高,但是在多核時代其實也不是特別準了。主要是現代處理器體系結構的以下特點影響造成的:

  1. 亂序執行導致測得不準
  2. 如果進程進行了切換,下一次運行在其它CPU核心上,CPU核心計時器不同步影響特別大

並且它的單位是執行時鐘週期數,由於自動調頻的存在,將CPU時鐘週期數轉化爲計時單位秒比較困難,但是在體系結構分析中可能比較有用。

具體參考多核時代不宜再用 x86 的 RDTSC 指令測試指令週期和時間

使用clock_gettime()函數

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

int main(int argc, char** argv) {
    struct timespec start_cnt, end_cnt;
    clock_gettime(CLOCK_MONOTONIC, &start_cnt);
    // ...
    clock_gettime(CLOCK_MONOTONIC, &end_cnt);
    double t = (end_cnt.tv_sec - start_cnt.tv_sec) * 1000.0 + (end_cnt.tv_nsec - start_cnt.tv_nsec) / 1000000.0;
    printf("Use time: %f ms. \n", t);  // 單位爲毫秒
    return 0;
}

使用clock_gettime()函數達到的精度爲ns,屬於非常準的計時了。

使用gettimeofday()函數

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

int main(int argc, char** argv) {
    struct timeval tv;
    double start_cnt, end_cnt;
    gettimeofday(&tv, NULL);
    start_cnt = tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
    // ...
    gettimeofday(&tv, NULL);
    end_cnt = tv.tv_sec * 1000.0 + tv.tv_usec / 1000.0;
    double t = end_cnt - start_cnt;
    printf("Use time: %f ms. \n", t);
    return 0;
}

使用gettimeofday()函數的精度爲us,屬於比較準的計時了。

使用times()函數

times()函數計時精度比較低,計時精度一般是10ms,不推薦。參考linux times函數分析

總結

C/C++代碼裏面有各種各樣的計時函數。其中需要注意一點的是,如果程序運行過程中涉及了上下文切換,計算的時間還準嗎?這裏除了clock()和times()函數會去掉本進程休眠的時間外,其它的都不能去掉進程休眠的影響。但是考慮到一般對於計算密集的程序,如果不涉及到IO或者直接使用sleep進行休眠,基本不會進行上下文切換,對於程序計時的影響不大。

對於程序計時,筆者對他們封裝了一下,具體見我的碼雲。關於獲取程序運行時鐘週期數,本來打算將其擴展到ARM的,後期再說吧!

避免CPU核心自調整頻率的坑

最近在測試程序運行的時候,程序運行時間對比老是跳來跳去。
經過多方排查,發現是CPU的頻率動態調節的坑。

CPU頻率動態調節對運行時間短的程序影響特別大。特別是2秒以內的程序。
如果是程序運行超過2秒的程序,由於計算密集造成系統提升頻率的時間大概在半秒到1秒之間,所以影響不是特別大。
至於是如何發現的,是在perf工具進行性能分析的時候發現的。

那麼如何避免這個坑呢?

串行程序

如果是串行運行程序,推薦方法是首先把一個特定的CPU的模式改爲peformance模式(即使用最大頻率運行模式),然後設置程序在這個特定的CPU上面運行。程序運行完成之後再還原爲系統默認的conservative模式。
這樣做的好處有以下幾點:

  1. 放到一個特定的編號的CPU上面,其它進程對其搶佔的機率比較小,可能幾乎就沒有進程的上下文切換開銷,避免了進程上下文切換的干擾。
  2. 在一個特定的CPU上面運行便於監控。

具體做法如下(以使用21號CPU爲例):

  1. 將在程序代碼裏面加入如下的設置cpu的函數,並且在主函數運行開始便指定cpu:
#define _GNU_SOURCE   //這行要加
#include <sched.h>
void set_cpu(int cpu) {
    cpu_set_t cpu_mask;
    CPU_ZERO(&cpu_mask);
    CPU_SET(cpu, &cpu_mask);
    sched_setaffinity(0, sizeof(cpu_mask), &cpu_mask);
}
int main(int argc, char** argv) {
    set_cpu(21); //設置當前進程運行在21號CPU
    // ...
    return 0;
}
  1. 設置21號CPU的模式爲performance:
su root # 需要root權限
cpupower -c 21 frequency-set -g performance
exit    # 設置完之後一定要退出root
  1. 運行加入set_cpu(21)的代碼。
  2. 測試完成後,設置21號CPU爲默認的conservative模式:
su root # 需要root權限
cpupower -c 21 frequency-set -g conservative
exit    # 設置完之後一定要退出root

**PS:**設定進程運行在特定的CPU上面,也可以使用taskset命令。

並行程序

推薦做法是把所有cpu設爲performance模式,之後測試程序。測完之後記得將CPU改爲默認模式。

su root # 需要root權限
cpupower frequency-set -g performance # 設置所有CPU爲performance模式
exit    # 設置完之後一定要退出root
# 運行你的測試
su root # 需要root權限
cpupower frequency-set -g conservative # 運行完成後調爲默認模式
exit    # 設置完之後一定要退出root

關於爲什麼最後還要調回默認模式

對於服務器來說,第一個因素肯定是耗電呀!可能有人說服務器在機房,不用你管耗不耗電。但是就是動一動手指的事,就算爲地球環保做貢獻吧!

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