再谈JavaScript时钟中的16ms精度问题.
上一篇BLOG中,通过测试我们发现 JavaScript的时钟是16ms的间隔. 对于IE来说,每次总会发生16ms的间隔;对于firefox来说,会存在0ms的间隔. 对于后者,我曾解释说:可能是Java使用了自己的时钟.
先说第二种情况,对于firefox中的js引擎,我尚未去看代码,因此我只说"可能",但后来hax来说,firefox的JS引擎仍是C写的,这才想起的确如此.所以这里先说,我前面关于firefox的问题的解释是错的.
再说16ms的问题. 我其实也怀疑,为什么是16ms,而不是其它的什么值呢?
hax给了我很多信息.我这里来整理一下:
首先是取时间值.也就是我们在JavaScript中用new Date()得到时间值采用的方法其实是不准确的. 该值总会是15~16ms的间隔值,其原因在于:
---------------
Windows系统获取时间主要是用下面的几种方法
一:GetTickCount()
这个就是用的上篇里说的系统时钟中断
Windows NT 3.5 及更高版本,精度为 10ms(100Hz)
Windows NT 3.1 ,精度为 16ms(60Hz)
Windows 95 及更高版本,精度为 55ms(18.2Hz)
(对于Windows XP(NT5.0) 及更高版本,实测得的精度为 16ms)
二:timeGetTime()
精度约1ms(需通过其它API配合)
三:High-Resolution Timer
这种方法就使用了CPU的RTC
QueryPerformanceCounter() 配合 QueryPerformanceFrequency(),适用于高精度应用场合
参见:http://www2.matrix.org.cn/thread.shtml?topicId=10491&forumId=1&fid=1
---------------
也就是说,如果JavaScript的new Date()采用getTickCount()来实现,那么它必然是返回16ms间隔的时间值的.
举例来说,你可能写一个超大循环用来收集一批Date()对象,最终你会发现,大多数时候是一样的值,而每两
批时间值的差值总在15~16ms.
接下说时间精度.也就是讨论setTimeout/setInterval()为什么是16ms,而另外一些人/资料会提及到10ms这个精度值.首先我们来看时钟的实现:
---------------
一、使用系统时钟SetTimer()
系统时钟是采用向窗体发WM_TIMER事件来激活处理例程的。而且同一个窗体消息队列中只允许同时存在一个WM_TIMER,因此它可能被丢失消息。
二、使用timeSetEvent()
可以使用timeSetEvent()来设置时钟。缺省情况下,他是16ms精度的。另外,同进程中同时最多有16个timeSetEvent()。
三、在线程中使用sleep()
Zhe曾用C代码做过测试,证明sleep()的精度是10ms。也就是说,sleep(1)产生的效果最低限也是10ms的间隔。
参见:http://dev.cbw.com/vc/progress/200510315005_4279849.shtml
---------------
timeSetEvent缺省情况下是16ms/10ms间隔的,更确切地说,是“对于Intel 芯片的精度为16ms,对于MIPS芯片的精度为10ms”。这一点在下面这份文档中有说明:
http://www.wanfangdata.com.cn/qikan/periodical.Articles/jdgc/jdgc99/jdgc9903/990318.htm
timeSetEvent()可以通过一些API:timeBeginPeriod()来调整计时精度。这一点请参考:
http://www.vckbase.com/document/viewdoc/?id=1234
最后,我们来讲结论。对于JavaScript来说,由于new Date()只需要得到毫秒值,并不需要过高的精度,因此最合理的方法当然是调用GetTickCount()来取值,而不是采QueryPerformanceCounter() 配合 QueryPerformanceFrequency()来得到高精度的时间值。由于GetTickCount()自身的限制,JavaScript的new Date()得到的时间隔是16ms精度的。——对于win98或其它系统来说,这个值可能是58ms/10ms。
对于定时器来说,JavaScript应该会是使用timeSetEvent()来做定时器。因为如果用线程+sleep()会存在较大的开销(事实上我出观察到线程计数是没有变化的),而采用SetTimer()会存在消息队列的问题。所以采用timeSetEvent()是最实际的方案。——至于16个timeSetEvent()的限制,可以通过同一个timeSetEvent()中处理多个例程来规避。因此,由于timeSetEvent()自身在缺省状态下的限制,因此导致了16m时间间隔。
对于FireFox中的和WebKit(safari)中的脚本引擎,因为是开源的,有兴趣的不妨自己读读相关的代码。就不再讲述了。
感谢Zhe和hax对我提供的帮助。本文基本上是资源整理贴。^.^