Usage of QueryPerformanceCounter in Gecko

It's known to be slow and having problems with unpredictable jumps back and forward, but only on a minority of platforms.  But still, some protection measures must be taken.

I've observed really significant performance issues only on older systems running Windows XP (32-bit).

Problems causing the biggest pain are:

  • QueryPerformanceCounter may jump against expected monotonicity when a thread switches a processor core (happens quite often, by the way) ; solution can be sticking one's thread to a single core.  This, though, happens on minority of systems like 64-bit processors running 64-bit Windows XP.
  • QueryPerformanceCounter may not exactly reflect time while a machine has been suspended ; or simply may not increase as expected.
  • When called very often, e.g. in cycles, it may influence even I/O operations performance.

A year or so ago I've implemented high resolution timer to be used in the Mozilla Platform.  The code, as is, is pretty complicated, it tries to calibrate QueryPerformanceCounter values against GetTickCount that is known to be very stable and also fast to call, but has, depending on a system, not better then 15.6 milliseconds interval of increasing.  I've chosen to calibrate QueryPerformanceCounter with GetTickCount based on this compare of Windows API timer functions.

Recently, I've got an idea to simplify the QueryPerformanceCounter usage implementation quite a lot.  It also comes from demand to prevent use of QueryPerformanceCounter for measuring timeouts longer then, say, 500ms.  It would be just wasting of resources.  GetTickCount may perfectly fulfill the purpose for longer durations.

The new code does the following: when the Now() function is called, the result is a complex value keeping both QueryPerformanceCounter and GetTickCount results.  When measuring duration between two calls to Now(), I can check how much QueryPerformanceCounter differentiates from GetTickCount in both timestamps.  If it is too much, I simply use GetTickCount to calculate the duration.  If it differs too much and the interval between the two timestamps is very short, I can even say QueryPerformanceCounter is not stable and consider not to use it at all.

Calculation of duration between two timestamps using QueryPerformanceCounter (QPC) and GetTickCount (GTC):

  • jitter = ABS((QPC1 - GTC1) - (QPC2 - GTC2))
  • if jitter is less then 15.6 x 2, then calculate and return duration as QPC1 - QPC2
  • jitter = jitter - 15.6 x 2
  • if jitter is more then 50ms per 1000ms of duration, then calculate and return duration as GTC1 - GTC2
  • and, if ABS(GTC1 - GTC2) is less then 2000ms, stop using QueryPerformanceCounter at all, it misbehaves

15.6 ms is result of GetSystemTimeAdjustment's timeIncrement.  2000ms is the "too short" duration to let QPC behave wrong.  The 50ms per 1000ms is empirically gathered number.  So far, I know it is able to detect really badly behaving QueryPerformanceCounter.

This radically simplifies the current code and from its nature solves the suspend/wake up issue.  System sleep time will always be correctly reflected by GetTickCount.  QueryPerformanceCounter inconsistency after waking the system up will simply be ignored.

This also allows a very simple implementation of NowLoRes() just by not storing QueryPerfomanceCounter result in the time stamp value.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.