由於底層邏輯實現不同作業系統區別很大,所以乾脆分篇來說。
主要講一下Time、TimeTicks兩個類裡面對於時間戳的實現,其餘的運算子過載、邊緣工具方法就不看了,先是Time。
Time
類本身的說明在上一篇有,這裡就去掉了。
class V8_BASE_EXPORT Time final : public time_internal::TimeBase<Time> { public: // Contains the nullptr time. Use Time::Now() to get the current time. constexpr Time() : TimeBase(0) {} // Returns the current time. Watch out, the system might adjust its clock // in which case time will actually go backwards. We don't guarantee that // times are increasing, or that two calls to Now() won't be the same. static Time Now(); // Returns the current time. Same as Now() except that this function always // uses system time so that there are no discrepancies between the returned // time and system time even on virtual environments including our test bot. // For timing sensitive unittests, this function should be used. static Time NowFromSystemTime(); // ... };
從註釋可知,這裡的Now是返回國際時間戳的通用方法,但是作業系統可能會對返回值做修正,所以是有一定風險的。第二個NowFromSystemTime使用的系統時間比較準確,求精確的情況下考慮使用這一個。
但是在mac上,這兩個方法是一樣的。
#elif V8_OS_POSIX Time Time::Now() { // ... } Time Time::NowFromSystemTime() { return Now(); }
這就很蠢了,可能是該作業系統不存在修正時間戳的情況,所以沒必要分辨這兩個方法了。
所以對於兩種方式的解析就變成了一個,集中來看Now的實現。
// #ifndef _STRUCT_TIMEVAL // #define _STRUCT_TIMEVAL struct timeval // _STRUCT_TIMEVAL // { // __darwin_time_t tv_sec; /* seconds */ // __darwin_suseconds_t tv_usec; /* and microseconds */ // }; Time Time::Now() { // 內建結構體 見上面 struct timeval tv; // Linux內建時間函式 int result = gettimeofday(&tv, nullptr); // 返回值檢測 DCHECK_EQ(0, result); USE(result); return FromTimeval(tv); }
這裡的用的都是Linux內建的方法,timeval結構體專門用來獲取返回的時間,可以精確到微秒,也就是秒/毫秒/微秒的精度。
結構體兩部分分別儲存當前時間戳的秒部分、微秒部分,型別均為long,下面用一個簡單例子來展示。
int main() { struct timeval tv; gettimeofday(&tv, nullptr); cout << "current seconds is " << tv.tv_sec << endl; cout << "current microseconds is " <<tv.tv_usec << endl; }
在瀏覽器下面同時用Date.now()做一個對比,由於還是有一定的時間差,所以微秒部分肯定對不上的。
兩者輸出對比如下。
在秒的部分完全對上了,微秒那塊就別在意了,我可沒有神手速。
這樣,就通過系統API得到了當前時間戳,下面就是對兩個部分做一個處理。
Time Time::FromTimeval(struct timeval tv) { // 1秒 = 1000 * 1000微秒 這裡做的合法性檢測 DCHECK_GE(tv.tv_usec, 0); DCHECK(tv.tv_usec < static_cast<suseconds_t>(kMicrosecondsPerSecond)); // 當秒、微秒都返回0 返回預設構造類 如下 // constexpr Time() : TimeBase(0) {} if (tv.tv_usec == 0 && tv.tv_sec == 0) { return Time(); } // 如果返回值達到了最大值 則返回最大值 max也是內建方法 if (tv.tv_usec == static_cast<suseconds_t>(kMicrosecondsPerSecond - 1) && tv.tv_sec == std::numeric_limits<time_t>::max()) { return Max(); } // 這裡返回微秒單位的數值 return Time(tv.tv_sec * kMicrosecondsPerSecond + tv.tv_usec); }
比較簡單,看一下注釋就懂了,最後返回的是以微秒為單位的一個長整數,而JS中的Date.now()返回的則是毫秒單位,略有不同。
TimeTicks
class V8_BASE_EXPORT TimeTicks final : public time_internal::TimeBase<TimeTicks> { public: constexpr TimeTicks() : TimeBase(0) {} static TimeTicks Now(); static TimeTicks HighResolutionNow(); static bool IsHighResolution(); private: friend class time_internal::TimeBase<TimeTicks>; explicit constexpr TimeTicks(int64_t ticks) : TimeBase(ticks) {} };
這個類看看就好了,跟上面那個類似,也有兩個方法,一個是更精確的。
然而,兩個方法也是一個,在mac上不存在精細度(windows上都有區別,下篇搞),V8在內部直接寫了如下注釋。
#error platform does not implement TimeTicks::HighResolutionNow.
所以,只看Now的實現。
struct mach_timebase_info { uint32_t numer; // 分子 uint32_t denom; // 分母 }; TimeTicks TimeTicks::Now() { int64_t ticks; static struct mach_timebase_info info; if (info.denom == 0) { kern_return_t result = mach_timebase_info(&info); } ticks = (mach_absolute_time() / Time::kNanosecondsPerMicrosecond * info.numer / info.denom); // Make sure we never return 0 here. return TimeTicks(ticks + 1); }
這裡涉及2個內建方法和1個內建結構體,挨個介紹一下。
- mach_timebase_info結構體作為引數傳入同名函式
- mach_timebase_info方法返回兩個因子,將返回的分子除以分母可以得到一個基準引數(找不到Linux的官方API文件,還是windows好啊),具體解釋有興趣可以去檢視
- mach_absolute_time方法返回一個系統從啟動開始保持執行的一個絕對時間,參考windows的QPC,單位為納秒
唯一有價值的就是那個單位,由於返回的絕對時間單位是納秒,所以需要除以TimeConstants裡面的常數,最後與基準引數相乘,最終得到一個硬體時間戳。
本地做一個實驗。
int main() { static struct mach_timebase_info info; mach_timebase_info(&info); cout << "numer is " << info.numer << endl; cout << "denom is " << info.denom << endl; cout << "absolute time is " << mach_absolute_time() << endl; cout << "current timestamp is " << (info.numer / info.denom) * (mach_absolute_time() * 1e-9) << endl; }
這樣得到最終的結果理論上就是我mac電腦的活躍秒數。
7000秒,也就是大約2個小時吧,看來還是很準確的,有興趣的可以自行實驗。
下一篇換windows,apple的apidoc真是一坨屎,根本跟微軟沒法比。