Deep into the win Chapter of V8 Engine-Time Core Method

The source code of the last article is very boring. The official documents are as bad as black cotton.

This article talks about the implementation of timestamp on windows operating system. Because of class declaration and method interpretation, the previous one has been pasted, so this time it directly corresponds to the corresponding version of the code.

 

windows is very different from mac in that it implements a new Clock class to manage time, as follows.

// 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_;
};

As can be seen from the annotations and method names, windows completely replaces the old Time and TimeTicks with this new class, because this method has better performance, and this class synchronizes data periodically with the system time.

Let's begin formally.

 

Starting with the Now method, we can see how the windows system acquires the local timestamp.

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(); }

The definition of this method is not general. If a special macro is used directly, the macro will not expand. In short, it will be lazy to load. When it is called, it will allocate space to generate a Clock class. After initialization, the second call will return directly and be understood as an example.

Looking directly at the macro's return type, it happens to be the lock above, which has only one parametric constructor and initializes two timestamp attributes.

Look at that first, which is the timestamp of system time.

static Time GetSystemTime() {
    FILETIME ft;
    ::GetSystemTimeAsFileTime(&ft);
    return Time::FromFiletime(ft);
  }

FILETIME and GetSystemTime AsFileTime are both windows APIs, which can get the date and time of the current system, but the return value is strange.

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).

This is the statement and explanation of the structure. High and Low represent the high position and position of time respectively, and that method is used in conjunction with this.

As you can see from the above, the return time of this API started from January 1, 1601, and I don't know what happened that year.

Write a test code below.

int main()
{
    FILETIME ft;
    LARGE_INTEGER t;
    ::GetSystemTimeAsFileTime(&ft);
    t.LowPart = ft.dwLowDateTime;
    t.HighPart = ft.dwHighDateTime;
    cout << t.QuadPart << endl;
}

The output is 132034487665022709, which is multiplied by 100 nanoseconds and converted.

Since the benchmark was 1601 and Date was calculated from 1970, the difference was 369 years, just 2019, which is reasonable.

Look at the V8 process.

// Time between windows epoch and standard epoch.
static const int64_t kTimeToEpochInMicroseconds = int64_t{11644473600000000};

Time Time::FromFiletime(FILETIME ft) {
  // Special case handling
  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();
  }
  // conversion
  int64_t us = (static_cast<uint64_t>(ft.dwLowDateTime) +
                (static_cast<uint64_t>(ft.dwHighDateTime) << 32)) / 10;
  return Time(us - kTimeToEpochInMicroseconds);
}

Let's take a look at the special situation. The main conversion step is to simply put the high and low values together and divide them by 10, then the unit changes from 100 nanoseconds to microseconds.

The final calculation is also to balance the differences between standard timestamps and windows timestamps, as follows.

Why not 1970-1601 = 369 years? Because there is a leap year in the middle, which is reasonable.

Finally, the standard timestamp in microsecond units is obtained, and the value is assigned to the attributes of the class.

 

Back to the original Now method, after initialization, Clock's own Now method is called to get the final timestamp, as follows.

Time Now() {
  // An Error Critical Value
  const TimeDelta kMaxElapsedTime = TimeDelta::FromMinutes(1);
  
  // I don't want to parse everything about locks at the moment.
  MutexGuard lock_guard(&mutex_);

  // Get the current hardware timestamp and local timestamp again
  TimeTicks ticks = GetSystemTicks();
  Time time = GetSystemTime();

  // Error correction here
  TimeDelta elapsed = ticks - initial_ticks_;
  // 1.The current time is less than the initialization time. Refer to the comments on class methods in the previous article.(the system might adjust its clock...)
  // 2.When the time difference between hardware timestamps exceeds the critical value, it can be concluded that the initialization time is totally untrustworthy.
  if (time < initial_time_ || elapsed > kMaxElapsedTime) {
    initial_ticks_ = ticks;
    initial_time_ = time;
    return time;
  }

  return initial_time_ + elapsed;
}

Although the timestamp is obtained in the constructor, V8 takes into account the errors caused by function calls, system corrections and other reasons (such as the first initialization), and revises it again. Specific operations and reasons can be directly seen in the annotations. Finally, the returned timestamp is calculated theoretical local timestamp plus hardware timestamp difference.

As for NewFromSystem Time, it's simpler. In mac, these two methods are one, and in windows, they are as follows.

Time NowFromSystemTime() {
  MutexGuard lock_guard(&mutex_);
  // Update two timestamps
  initial_ticks_ = GetSystemTicks();
  initial_time_ = GetSystemTime();
  // Return the latest timestamp directly
  return initial_time_;
}

Without calculating anything, you can directly return the timestamp of the system API to understand these two methods with annotations.   

 

Embarrassed, I didn't expect V8 to use both timestamps in the Time phase. Looking at the implementation of TimeTicks, I found that it was still interesting, so this article is too long to write.

Keywords: PHP Windows Mac less

Added by rakennedy75 on Mon, 27 May 2019 20:59:05 +0300