深刻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的实现,发现还有点意思,因此这一篇先这样了,太长了写的累。
相关文章
相关标签/搜索