woensdag 9 december 2009

Windows tijd: in stapjes van 15ms!

Soms wordt je onaangenaam verrast: je meet de tijd van alle SQL queries in je programma met "MillisecondsBetween" en je krijgt als resultaat: 0, 0, 0, en 15ms...

Hmmm, dit is te mooi om waar te zijn, zo snel is MS SQL Server nou ook weer niet! :-)
Er ging bij mij toen wel een lampje branden, ik eens eerder zoiets gezien met "Sleep" en "GetTickCount".

Thread time slices
In Windows (en andere systemen) worden threads 1 voor 1 op een CPU uitgevoerd. Elke thread heeft een "time slice" van 15ms, waarin hij de volledige CPU tot zijn beschikking heeft. Na 15ms breekt Windows de thread af, slaat de inhoud van de CPU registers e.d. op, en laad en start een andere thread ("context switch"). Een "time slice" kun je afbreken door een wait function aan te roepen, zoals "Sleep", "WaitForSingleObject", etc. Op deze manier kan een andere thread de kostbare CPU tijd gebruiken.


Consequenties
De default time slice van 15ms heeft nogal wat consequenties voor het programmeren: zo gaat een "Sleep(1)" niet 1ms wachten, maar 10 - 15ms! (afhankelijk van andere threads etc). Maar ook andere tijd functies zoals "GetTickCount", "Now" (Delphi) of "GetLocalTime" (Windows) werken in stapjes van 15ms!

Nauwkeurige tijdmeting
Gelukkig zijn er andere manieren om het aantal ms te bepalen:
  • hardware timer gebruiken: met QueryPerformanceFrequency + QueryPerformanceCounter kun je met micro en nano secondes werken!
  • kortere "time slice" instellen met "timeBeginPeriod", bijv een time slice van 1ms met "timeBeginPeriod(1)". Dit heeft echter wel nadelen: veel meer context switches, dus meer overhead!
Zelf heb ik andere "Now" functie in Delphi geschreven, die op basis van QueryPerformanceCounter etc werkt (dan werkt alles in 1x op de ms nauwkeurig, of zelfs op de micro seconde).
Ik vervang ("detour") de oude "Now" functie door mijn "NowExact" door middel van "KOLDetours.pas":
OldNow := KOLDetours.InterceptCreate(@Now, @NowExact);
Anekdote
Ik heb eens bij een bedrijf de RS232 communicatie met een PLC flink versneld: in een loopje werd na elke ontvangen character een "Sleep(1)" aangeroepen. Dit heb ik met een factor 10 versneld door pas na een lege character een "Sleep(1)" te doen, en die door middel van "timeBeginPeriod(1)" ook echt 1ms wacht ipv 10 to 15ms!

Geen opmerkingen: