通过 RDTSC 指令从 CPU 寄存器中直接获取系统时钟
很多時候我們使用函數 gettimeofday 以及 clock_gettime 作為我們獲取 wall lock的時鐘函數。
因為這兩種函數是 glibc 提供的用戶封裝,簡單易用,而且能夠精確到 ns,對于大多數的時鐘需求場景都已經夠用了。
但是如果 我們的應用 調用時鐘頻繁 且 對 多線程場景下時鐘的單調性要求不是特別高的時候 ,以上提到的兩個 glibc 函數的開銷其實是有點大的。
gettimeofday底層是會調用到系統調用sys_gettimeofday,執行邏輯會陷入內核,將內核保存的 walllock 和 jiffies 做一個綜合的精度計算,將計算結果從內核態拷貝到用戶態,交給timeval,這整個過程可以說開銷是比較大的。(需要系統調用)clock_gettime,這個函數同樣的需要執行一個系統調用 ,并且做 精度處理之后才返回給用戶態。
舉個例子,我們分布式系統中的打點系統可以說非常重要,事關整個系統健康情況的展示,能夠幫助我們提前或者及時得準確發現系統中潛在的問題, 但是因為這一些打點需要在關鍵路徑上,往往對整個系統性能會有一定的損耗。而這一些打點 并不影響整個分布式系統中 為分布式事務選擇的時鐘(可以用兩套時鐘),只需要能夠計算出一個操作前后的時間差即可,保持單線程內的單調性即可。
所以,如果我們的系統中所有的時鐘都采用 glibc 實現的兩種方式,對于頻繁的打點系統來說 代價實在是有點大,而且對關鍵路徑的性能都會有一定的影響。
今天介紹一個可以從 CPU 寄存器中直接獲取 walllock 的匯編指令 RDTSC。
我們目前大多數的服務器系統是 AMD64 , x86_32/x86_64 位,可以通過如下 代碼獲取:
#include <stdio.h>
#include <inttypes.h>int main() {uint64_t a,d,t;__asm__ volatile("rdtsc" : "=a"(a), "=d"(d));t = ((d << 32) | a);printf("%lu\n",t);return 0;
}
拿到的 walllock 是 自1970.1.1 到現在 的CPU周期數,對于 GHZ 的CPU 來說單位就是ns。
我們的服務器主板有一個即使機器斷電重啟也不會重設的存儲單元 RTC,它會將當前機器到 1970.1.1 經過的時間轉化為當前機器的CPU時鐘周期并存儲下來。
我們的CPU 在完成一個時鐘周期之后會在 MSR(model-specific register) 寄存器中存儲計數(64位),我們的 RDTSC (read Time-Stamp Counter) 指令就是直接從 MSR 寄存器中取出計數信息,其中的 高32位 放在 edx 寄存器中,低 32位放在eax 寄存器中。
這也就是為什么 我們在AMD64 下執行這個指令需要在取到結果之后對存儲高 32位數據 的 d 變量做一個移位。
這樣我們就可以完整得實現一個 在AMD64 架構下的 rdtsc 指令的時間統計:
#include <stdio.h>
#include <inttypes.h>static inline
uint64_t rdtsc_time() {uint64_t a,d;__asm__ volatile("rdtsc" : "=a"(a), "=d"(d));return ((d << 32) | a);
}static inline
uint64_t clock_to_nsec(uint64_t begin, uint64_t end) {double clock_diff;// 這里做一些簡單的單調性處理, 防止 end < begin.if (end < begin)return (0);clock_diff = (double)(end - begin);return ((uint64_t)(clock_diff);
}int main() {uint64_t start_ns = rdtsc_time();// do some code......uint64_t end_ns = rdtsc_time();fprintf(stdout, "code execute time(ns) : %lu\n", clock_to_nsec(start_ns, end_ns));return 0;
}
為了兼容不同的平臺,像 i386 以及 arm 平臺,所以,我們需要變更 rdtsc_time 函數為如下形態(不同架構下的獲取時鐘指令有一些差異):
static inline uint64_t
rdtsc_time(void)
{
#if defined(__i386) // intel 80386 架構{uint64_t x;__asm__ volatile("rdtsc" : "=A"(x));return (x);}
#elif defined(__amd64) // amd x86_64架構{uint64_t a, d;__asm__ volatile("rdtsc" : "=a"(a), "=d"(d));return ((d << 32) | a);}
#elif defined(__aarch64__) // arm 64為架構{uint64_t t;__asm__ volatile("mrs %0, cntvct_el0" : "=r"(t));return (t);}
#elsereturn (0);
#endif
}
參考:
https://www.amd.com/system/files/TechDocs/24594.pdf
https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-32-ia-64-benchmark-code-execution-paper.pdf
總結
以上是生活随笔為你收集整理的通过 RDTSC 指令从 CPU 寄存器中直接获取系统时钟的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 求一个qq头像和网名一套!
- 下一篇: 成都一般的医院打胎要多少钱?