HRT(High Resolution Timing, 高精度计时)在一些场合有很大的作用,比如游戏开发中,需要精确的计算两帧之间的时间差。
在JS中常常用(new Date()).getTime()
来获取毫秒级的时间戳,虽然是毫秒级,但事实上它的真实精度只能达到大概16ms的级别。例如
1 2 3 |
while (true){ console.log((new Date()).getTime()); // 这样死循环浏览器会跪的,责任自负 } |
会发现它事实上大概16ms才跳一次,也许是17ms、又或者15ms吧,反正实际精度是有限的——什么?你跟我说是1ms?我告诉你那是因为新的系统或者浏览器使用了更高精度——但这不影响这篇文章的内容……
这对于日常应用来说完全够用了,但是对于游戏这样的场合,高精度计时就有它不可取代的意义了。
故事从这里开始
上面的获得毫秒级时间戳的方式之所以精度有限,是因为它的实现方式,以及它“绝对时间”的定义。
以Windows为例,这一类时间戳所使用的系统调用,比如GetSystemTime()
MSDN、GetTickCount()
MSDN,其函数的取值并不是实时的,而是通过硬件的时钟中断被动刷新的,这里的刷新间隔“正好”就是上面那个16ms。以GetSystemTime()
为例,它返回的是SYSTEMTIME
结构体,这用来进行时间日期处理的,因为时间日期处理通常根本不需要也不应该用那么高的精度(甚至很多时候只需要秒级别的精度),所以(new Date()).getTime()
通过它们实现的确是可以胜任的。
现在我们明白了,靠这个时间戳是不能实现高精度计时的。
在Windows上,常常有两种高精度计时的方式:
第一种是timeGetTime()
MSDN,它能返回系统启动到现在所经过的毫秒数,精度是1ms,因为它是32位的,所以大概49.71天会溢出清零。
第二种QueryPerformanceCounter()
配合QueryPerformanceFrequency()
MSDN实现,能够实现微秒级别的计时精度,对于大多数场合而言都够了。
当然还有更先进的方法,是通过CPU中的硬件计数器,和CPU每个时钟周期的时间,计算出更精确的时间(通常是纳秒级别的),对精度要求极高的场合这是最精确的选择了。
通常在使用固定位数的情况下,精度越高意味着计时的范围越小,这就不罗嗦了。
回到JavaScript中来
上面那些乱七八糟系统调用其实更咱们都没什么太大关系,我们能干什么完全看运行环境乐意给什么。
在webkit中提供了performance.now()
参考文献来获取一个毫秒级的浮点数时间戳,我没查到资料它的有效精度是多少,不过既然给了个浮点数那就这么用着吧,我们就当它是微秒级的了!
在node.js中,有process.hrtime()
DOC,返回的是一个数组[seconds, nanoseconds]
,看起来它具有纳秒级别的精度?且信了吧。
综合一下,我写了下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
exports.time = (function(){ if (typeof window !== 'undefined'){ // 浏览器 if (typeof window.performance !== 'undefined' && typeof performance.now !== 'undefined'){ // support hrt return function(){ return performance.now(); }; }else{ // oh no.. return function(){ return (new Date()).getTime(); }; } }else{ // node.js return function(){ var diff = process.hrtime(); return (diff[0] * 1e9 + diff[1]) / 1e6; // nano second -> ms }; } })(); |
有了上面的代码(gist),我们就能写一个秒表神马的,在做性能测试的时候就用得上了。
最后还是要唠叨一句,HRT是用来计算时间差的,不是用来计算现实中时间(挂钟时间)的。
下一篇文章中,将会对JS中的时间精度进行进一步的讨论,对象自然就是setTimeout/setInterval
了!