深入V8引擎-Time核心方法之mac篇
由于底层逻辑实现不同操作系统区别很大,所以干脆分篇来说。
主要讲一下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的真是一坨屎,根本跟微软没法比。