3

Recently I was doing some deep timing checks on a DirectShow application I have in Delphi 6, using the DSPACK components. As part of my diagnostics, I created a Critical Section class that adds a time-out feature to the usual Critical Section object found in most Windows programming languages. If the time duration between the first Acquire() and the last matching Release() is more than X milliseconds, an Exception is thrown.

Initially I set the time-out at 10 milliseconds. The code I have wrapped in Critical Sections is pretty fast using mostly memory moves and fills for most of the operations contained in the protected areas. Much to my surprise I got fairly frequent time-outs in seemingly random parts of the code. Sometimes it happened in a code block that iterates a buffer list and does certain quick operations in sequence, other times in tiny sections of protected code that only did a clearing of a flag between the Acquire() and Release() calls. The only pattern I noticed is that the durations found when the time-out occurred were centered on a median value of about 16 milliseconds. Obviously that's a huge amount of time for a flag to be set in the latter example of an occurrence I mentioned above.

So my questions are:

1) Is it possible for Windows thread management code to, on a fairly frequent basis (about once every few seconds), to switch out an unblocked thread and not return to it for 16 milliseconds or longer?

2) If that is a reasonable scenario, what steps can I take to lessen that occurrence and should I consider elevating my thread priorities?

3) If it is not a reasonable scenario, what else should I look at or try as an analysis technique to diagnose the real problem?

Note: I am running on Windows XP on an Intel i5 Quad Core with 3 GB of memory. Also, the reason why I need to be fast in this code is due to the size of the buffer in milliseconds I have chosen in my DirectShow filter graphs. To keep latency at a minimum audio buffers in my graph are delivered every 50 milliseconds. Therefore, any operation that takes a significant percentage of that time duration is troubling.

Robert Oschler
  • 14,153
  • 18
  • 94
  • 227
  • 2
    How are you measuring the time? – Jon Skeet Nov 30 '11 at 08:10
  • I am checking the system clock. In Delphi, that is with the "Now" function. – Robert Oschler Nov 30 '11 at 08:13
  • 1
    That's a problem to start with - that's probably the granularity of the system clock. – Jon Skeet Nov 30 '11 at 08:14
  • Wouldn't I always get a time-out then instead of sometimes? Or does the amount of error float? In either case, what else would I use? Performance counters seem to be a real snake pit due to CPU speed stepping so what else is there? – Robert Oschler Nov 30 '11 at 08:17
  • I'm not sure whether you *can* effectively write a timeout for less than 10ms using the system clock, to be honest. – Jon Skeet Nov 30 '11 at 08:21
  • Does that also mean that specifying sub-16 millisecond time-outs in synchronization object calls (WaitFor) are also suspect? Or does Windows have a more precise resolution for WaitFor*() calls? Same question about the Sleep() function if you don't mind. – Robert Oschler Nov 30 '11 at 08:24
  • 1
    Not sure, to be honest. It may well depend on the version of Windows. I'm pretty sure `Sleep` will have a minimal "useful" value of 15ms. – Jon Skeet Nov 30 '11 at 09:13

3 Answers3

4

Thread priorities determine when ready threads are run. There's, however, a starvation prevention mechanism. There's a so-called Balance Set Manager that wakes up every second and looks for ready threads that haven't been run for about 3 or 4 seconds, and if there's one, it'll boost its priority to 15 and give it a double the normal quantum. It does this for not more than 10 threads at a time (per second) and scans not more than 16 threads at each priority level at a time. At the end of the quantum, the boosted priority drops to its base value. You can find out more in the Windows Internals book(s).

So, it's a pretty normal behavior what you observe, threads may be not run for seconds.

You may need to elevate priorities or otherwise consider other threads that are competing for the CPU time.

Alexey Frunze
  • 61,140
  • 12
  • 83
  • 180
  • 2
    Also, I'd like to add that random memory blocks may be paged to disk, so iterating a buffer list may cause a page fault and thread waits for memory to be read from disk. – Ruslan Yushchenko Dec 01 '11 at 13:14
2

sounds like normal windows behaviour with respect to timer resolution unless you explicitly go for some of the high precision timers. Some details in this msdn link

Matt
  • 8,367
  • 4
  • 31
  • 61
2

First of all, I am not sure if Delphi's Now is a good choice for millisecond precision measurements. GetTickCount and QueryPerformanceCoutner API would be a better choice.

When there is no collision in critical section locking, everything runs pretty fast, however if you are trying to enter critical section which is currently locked on another thread, eventually you hit a wait operation on an internal kernel object (mutex or event), which involves yielding control on the thread and waiting for scheduler to give control back later.

The "later" above would depend on a few things, including priorities mentioned above, and there is one important things you omitted in your test - what is the overall CPU load at the time of your testing. The more is the load, the less chances to get the thread continue execution soon. 16 ms time looks perhaps a bit still within reasonable tolerance, and all in all it might depends on your actual implementation.

Roman R.
  • 68,205
  • 6
  • 94
  • 158
  • The web docs I read indicated that detecting a CPU speed change reliably, which would invalidate a QueryPerformanceCounter() measurement, is no easy matter. Do you know of something easy to implement that would work? I'll take a look at GetTickCount, thanks. – Robert Oschler Dec 01 '11 at 16:24
  • 1
    `GetTickCount` is simple and reliable, `QueryPerformanceCounter` has way greater precision but might be inaccurate for long term measurements, see http://stackoverflow.com/questions/7583074/how-to-realise-long-term-high-resolution-timing-on-windows-using-c/7583144#7583144 – Roman R. Dec 01 '11 at 17:14
  • `Now()` in Delphi uses `GetLocalTime()` in its implementation which gives the same precision as `GetTickCount()`. `GetSystemTimeAsFileTime()` isn't any better even though it returns a value in multiple of 1E-7 seconds. `QueryPerformanceCounter()` is the only one that may help, despite all the problems with it. – mghie Dec 03 '11 at 15:48