深刻V8引擎-Time核心方法之win篇(1)

  上一篇的源碼看得十分無趣,官方文檔跟黑心棉同樣渣。
  這一篇講講windows操做系統上的時間戳實現,因爲類的聲明,方法解釋上一篇都貼過了,因此此次直接上對應版本的代碼。

  windows與mac很不同,實現了一個新的Clock類來管理時間,以下。
// We implement time using the high-resolution timers so that we can get
// timeouts which are smaller than 10-15ms. To avoid any drift, we
// periodically resync the internal clock to the system clock.
class Clock final {
 public:
  Clock() : initial_ticks_(GetSystemTicks()), initial_time_(GetSystemTime()) {}

  Time Now() { /* */ }

  Time NowFromSystemTime() { /* */ }

 private:
  static TimeTicks GetSystemTicks() { /* */ }

  static Time GetSystemTime() { /* */ }

  TimeTicks initial_ticks_;
  Time initial_time_;
  Mutex mutex_;
};複製代碼
  從註釋和方法名能夠看出,windows徹底用這個新類代替了老的Time、TimeTicks,由於這個方法擁有更好的性能,這個類同時會週期性的與系統時間同步數據。
  下面正式開始。

  先從Now方法看起,看windows系統是如何獲取本地的時間戳。
DEFINE_LAZY_LEAKY_OBJECT_GETTER(Clock, GetClock)

#define DEFINE_LAZY_LEAKY_OBJECT_GETTER(T, FunctionName, ...) \ T* FunctionName() { \ static ::v8::base::LeakyObject<T> object{__VA_ARGS__}; \
    return object.get();                                      \
  }

Time Time::Now() { return GetClock()->Now(); }複製代碼
  這個方法的定義也不通常,直接用了一個特殊宏,宏就不展開了,簡單說就是懶加載,調用的時候會分配空間生成一個Clock類,初始化完後第二次調用就直接返回了,當成一個單例來理解。
  直接看宏的返回類型,恰好是上面的Clock,該類只有一個無參構造函數,初始化兩個時間戳屬性。
  先看後那個,也就是系統時間的時間戳。
static Time GetSystemTime() {
    FILETIME ft;
    ::GetSystemTimeAsFileTime(&ft);
    return Time::FromFiletime(ft);
  }複製代碼
  這裏的FILETIME和GetSystemTimeAsFileTime都是windowsAPI,能夠獲取當前系統的日期和時間,可是返回值很奇怪。
typedef struct _FILETIME {
  DWORD dwLowDateTime;
  DWORD dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;複製代碼
Contains a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601 (UTC).
  這是結構體的聲明與解釋,High、Low分別表明時間的高位與地位,而那個方法就是配合這個使用的。
  能夠從上面看到,這個API返回的時間居然是從1601年1月1日開始算的,不知道那一年發生了什麼。
  下面寫一個測試代碼。
int main() {
    FILETIME ft;
    LARGE_INTEGER t;
    ::GetSystemTimeAsFileTime(&ft);
    t.LowPart = ft.dwLowDateTime;
    t.HighPart = ft.dwHighDateTime;
    cout << t.QuadPart << endl;
}複製代碼
  獲得的輸出爲132034487665022709,因爲單位是100納秒,因此這個數字乘以100的,而後換算一下。
  因爲基準是1601年,而Date是從1970年開始算,因此年份上差了369年,恰好是2019,很合理。
  來看看V8的處理。
// Time between windows epoch and standard epoch.
static const int64_t kTimeToEpochInMicroseconds = int64_t{11644473600000000};

Time Time::FromFiletime(FILETIME ft) {
  // 特殊狀況處理
  if (ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0) {
    return Time();
  }
  if (ft.dwLowDateTime == std::numeric_limits<DWORD>::max() &&
      ft.dwHighDateTime == std::numeric_limits<DWORD>::max()) {
    return Max();
  }
  // 換算
  int64_t us = (static_cast<uint64_t>(ft.dwLowDateTime) +
                (static_cast<uint64_t>(ft.dwHighDateTime) << 32)) / 10;
  return Time(us - kTimeToEpochInMicroseconds);
}複製代碼
  前面的特殊狀況看看就好了,主要是換算這一步,就是簡單的將高低位的數值拼到了一塊兒,除以10以後,單位從100納秒變成了微秒。
  最後的計算,也是爲了平衡標準的時間戳和windows時間戳二者的差別,以下。
  爲何不是1970 - 1601 = 369年整呢?由於中間有閏年,很合理。
  最後獲得微秒單位的標準時間戳,將該數值賦到類的屬性上。

  回到最初的Clock的Now方法,初始化完後,會調用Clock自身的Now方法獲取最終的時間戳,以下。
Time Now() {
  // 一個偏差臨界值
  const TimeDelta kMaxElapsedTime = TimeDelta::FromMinutes(1);
  
  // 我目前不想解析全部關於鎖的東西
  MutexGuard lock_guard(&mutex_);

  // 再次獲取當前的硬件時間戳與本地時間戳
  TimeTicks ticks = GetSystemTicks();
  Time time = GetSystemTime();

  // 這裏進行偏差修正
  TimeDelta elapsed = ticks - initial_ticks_;
  // 1.當前時間小於初始化時間 可參考上一篇中類方法的註釋(the system might adjust its clock...)
  // 2.硬件時間戳的時間差超過臨界值 這種狀況基本能夠認定初始化的時間徹底不可信了
  if (time < initial_time_ || elapsed > kMaxElapsedTime) {
    initial_ticks_ = ticks;
    initial_time_ = time;
    return time;
  }

  return initial_time_ + elapsed;
}複製代碼
  雖然在構造函數中獲取了時間戳,可是V8考慮到因爲函數調用、系統修正等緣由致使的偏差(好比第一次初始化),再次進行了修正,具體操做和緣由能夠直接看註釋,最後返回的時間戳是計算得到的理論本地時間戳加上硬件時間戳差值。
  至於NewFromSystemTime就比較簡單了,在mac中這兩個方法是一個,在windows裏以下。
Time NowFromSystemTime() {
  MutexGuard lock_guard(&mutex_);
  // 更新兩個時間戳
  initial_ticks_ = GetSystemTicks();
  initial_time_ = GetSystemTime();
  // 直接返回最新得到的時間戳
  return initial_time_;
}複製代碼
  不計算任何東西,直接返回系統API的時間戳,能夠配合註釋來理解這兩個方法。  

  尷尬了,沒想到V8在Time階段把兩個時間戳全用上了。稍微看了一下TimeTicks的實現,發現還有點意思,因此這一篇先這樣了,太長了寫的累。
相關文章
相關標籤/搜索