How to do something every millisecond or better on Windows
Asked Answered
U

4

8

This question is not about timing something accurately on Windows (XP or better), but rather about doing something very rapidly via callback or interrupt.

I need to be doing something regularly every 1 millisecond, or preferably even every 100 microseconds. What I need to do is drive some assynchronous hardware (ethernet) at this rate to output a steady stream of packets to the network, and make that stream appear to be as regular and synchronous as possible. But if the question can be separated from the (ethernet) device, it would be good to know the general answer.

Before you say "don't even think about using Windows!!!!", a little context. Not all real-time systems have the same demands. Most of the time songs and video play acceptably on Windows despite needing to handle blocks of audio or images every 10-16ms or so on average. With appropriate buffering, Windows can have its variable latencies, but the hardware can be broadly immune to them, and keep a steady synchronous stream of events happening. Even so, most of us tolerate the occasional glitch. My application is like that - probably quite tolerant.

The expensive option for me is to port my entire application to Linux. But Linux is simply different software running on the same hardware, so my strong preference is to write some better software, and stick with Windows. I have the luxury of being able to eliminate all competing hardware and software (no internet or other network access, no other applications running, etc). Do I have any prospect of getting Windows to do this? What limitations will I run into?

I am aware that my target hardware has a High Performance Event Timer, and that this timer can be programmed to interrupt, but that there is no driver for it. Can I write one? Are there useful examples out there? I have not found one yet. Would this interfere with QueryPerformanceCounter? Does the fact that I'm going to be using an ethernet device mean that it all becomes simple if I use select() judiciously?

Pointers to useful articles welcomed - I have found dozens of overviews on how to get accurate times, but none yet on how to do something like this other than by using what amounts to a busy wait. Is there a way to avoid a busy wait? Is there a kernel mode or device driver option?

Ubiety answered 10/8, 2011 at 0:45 Comment(6)
You can try using multimedia timers as mentioned in one of the answers, but this is also hardware dependent. Windows (and Linux for that matter) are simply not real time OSes and are not capable of doing things down at the 1ms scale. If you don't want to switch over to an RTOS you might be better off trying to find ethernet hardware that can accept a large buffer of packets from your Windows application and send these out bit by bit every 1ms or 100us tick.North
Excellent - that sounds like a really useful solution for my specific case. Any hints on what ethernet hardware might be capable of this?Ubiety
I don't even know if such a thing exists, I was just throwing out an idea :-) You should be able to pick up a microcontroller from TI, Motorola, PIC etc. that has an ethernet port and runs an RTOS that'll do this. You'd then have to talk to it from your PC over ethernet or RS232 to transfer the data. This solution is a lot more involved that simply writing a PC app, or finding off the shelf hardware that'll do what you're looking for, so it depends on the time & effort you're willing to put into this.North
UPDATE - edited the question to add emphasis in bold.Ubiety
If you do have a multicore, you can assign one core to do the timing (spinning a high resolution time source). See this for more details.Saccharase
> The expensive option for me is to port my entire application to Linux. Was this on about that Gartner Group FUD article from 2003 about the high total cost of ownership of Linux compared to windows? LOL.Normi
C
5

You should consider looking at the Multimedia Timers. These are timers that are intended to the sort of resolution you are looking at.

Have a look here on MSDN.

Crabber answered 10/8, 2011 at 0:52 Comment(3)
In theory, timeSetEvent() provides the kind of callback mechanism I would like to use. Should I be concerned about the fact that it is an obselete function? I don't see that queue timers are the replacement MS intends them to be - see virtualdub.org/blog/pivot/entry.php?id=272Ubiety
On closer reading, I find that the callback function is limited in the system calls it can make. In particular it can only send a message to talk to another thread. So if you want a very fast low level IO handler function linked to the timer, and have another thread serve it, it must be a slow UI thread. Either that or you take your chances with a pulsed event and hoping that the high priority thread you want to run in response to the event actually runs without significant delay. So not exactly the most compelling answer, but possibly better than porting a huge app to a new platform.Ubiety
Sorry - slight error - you can either set or pulse the event you fire through timeSetEvent(). A comment I read lead me to believe that these timer functions were deprecated because of the known issues with PulseEvent(). That may or may not be the case - I can't confirm. Still, as I read it, it's either run fast in the timer thread and have a slow server thread, or fire an event to a fast thread and have an unrestricted server thread, but take a gamble on thread scheduling. I really want to know how to write a driver for the High Performance Event Timer at this point!Ubiety
C
0

I did this using DirectX 9, using the QueryPerformanceCounter, but you will need to hog at least one core, as task switching will mess you up.

For a good comparison on tiemers you can look at

http://www.geisswerks.com/ryan/FAQS/timing.html

Corneous answered 10/8, 2011 at 0:58 Comment(3)
Thanks - that article was the best I had found to date, but the conclusion at the end was to wait for up to 1ms, and use 100% CPU time. If I want a 800us period, that sounds like a very, very bad idea!Ubiety
I had a game loop that kept looping (100% cpu) as my music needed to turn on/off at the correct precise ms (1ms resolution). It is not user friendly but if you let the system switch tasks you will be late. That is why I suggested hogging one core at least. You can resolve faster, so 800us is possible, btw.Corneous
100% CPU always sounds like a reason to go seeking a better solution, particularly since I have other real time threads that need to keep responding, but thanks.Ubiety
E
0

If you run into timer granularity issues, I would suggest using good old Sleep() with a spin loop. Essentially, the code should do something like:

void PrecisionSleep(uint64 microSec)
{
    uint64 start_time;

    start_time = GetCurrentTime(); // assuming GetCurrentTime() returns microsecs

    // Calculate number of 10ms intervals using standard OS sleep.
    Sleep(10*(microSec/10000)); // assuming Sleep() takes millisecs as argument

    // Spin loop to spend the rest of the time in
    while(GetCurrentTime() - start_time < microSec)
    {}
}

This way, you will have a high precision sleep which wouldn't tax your CPU much if a lot of them are larger than the scheduling granularity (assumed 10ms). You can send your packets in a loop while you use the high precision sleep to time them.

The reason audio works fine on most systems is that the audio device has its own clock. You just buffer the audio data to it and it takes care of playing it and interrupts the program when the buffer is empty. In fact, a time skew between the audio card clock and the CPU clock can cause problems if a playback engine relies on the CPU clock.

EDIT: You can make a timer abstraction out of this by using a thread which uses a lock protected min heap of timed entries (the heap comparison is done on the expiry timestamp) and then you can either callback() or SetEvent() when the PrecisionSleep() to the next timestamp completes.

Egor answered 10/8, 2011 at 2:4 Comment(4)
Surely Sleep() is part of the problem, not the solution - as geisswerks.com/ryan/FAQS/timing.html shows, even Sleep(1) for a nominal 1ms tends to give 10ms of sleeping, or 3-4ms if you happen to have an old Vaio. It's just too inconsistent.Ubiety
That's right. I had to do something like this sometime back and I resorted to experimentally figuring out what Sleep() granularity was and using that value in the algorithm above.Egor
-1. Primarily because answer doesn't address high resolution timers, with need for 1ms or better. Plus I don't see how it improves the accuracy of the timer for anything like small durations. If i'm reading this correctly, you're sleeping for the duration of the requested time - then spinning. For starters, for small durations Sleep will oversleep not undersleep (as @Ubiety correctly states) - then spinning is not good practice in general and soak up processor cycles. Please let me know if I've misread this.Crabber
Yes, oversleeping is possible. You should account for that when you sleep. As I stated, most OSes have a scheduling granularity. If the scheduling granularity is 10ms, then sleeping for 1ms wakes the thread up in the next scheduing cycle which can be anything between 1 and 10ms. You need to find an upper bound (say 10ms) and use that in the above algorithm. If you give a very small time period to the PrecisionSleep() function then it would skip the Sleep() all together and would directly go to busy wait. Keep in mind the kernel to user transition overhead before going with fine grained timers.Egor
E
0

Use NtSetTimerResolution when program starts up to set timer resolution. Yes, it is undocumented function, but works well. You may also use NtQueryTimerResolution to know timer-resolution (before setting and after setting new resolution to be sure).

You need to dynamically get the address of these functions using GetProcAddress from NTDLL.DLL, as it is not declared in header or any LIB file.

Setting timer resolution this way would affect Sleep, Windows timers, functions that return current time etc.

Equality answered 11/8, 2011 at 15:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.