When does GetTickCount consider the system to have started?


The Get­Tick­Count and Get­Tick­Count­64 functions return "the number of milliseconds that have elapsed since the system was started." (The 32-bit version wraps around after around 50 days.) But when exactly is the system considered to have started? Is it when power is applied to the computer? When the BIOS completes POST? When the user picks the operating system from the boot menu? When the kernel switches to protected mode?

It isn't defined exactly when the timer starts. Because that's not its purpose.

The purpose of Get­Tick­Count is to let you measure intervals of time. It provides a common clock source so that multiple components can coordinate their actions. It also allows you to retrieve the tick count at one point, then retrieve the tick count at another point, subtract them, and conclude how much time has elapsed between those two points. The absolute value of the tick count is not meaningful. The only way to extract meaning from it is to subtract it from another tick count to get the delta.

In fact, on debugging builds of Windows, the kernel artificially sets the Get­Tick­Count counter to "one hour before 32-bit timer tick rollover"; it effectively backdates the boot time by around 50 days. This is done to help identify bugs related to timer tick rollover.

If your goal is to measure operating system boot time from the application of power to the computer, then Get­Tick­Count is not going to be useful. After all, Windows isn't even running at the moment you apply power to the computer. The BIOS does its work without any operating system all, so Windows has no idea how long the BIOS took to POST. The text in MSDN could be a bit more explicit and say "elapsed since Windows started", or it could be pointlessly nitpicky and say "elapsed since the Windows HAL initialized the programmable interval timer."

Better would be if it simply described how the timer is intended to be used. "Get­Tick­Count returns a value which increases at a rate of 1000 per second." Perhaps with some clarifying text: "By convention, the zero point of the Get­Tick­Count counter is the approximate time the system booted. Note, however, that this convention is violated on occasion (such as on a checked build of Windows), so applications should not ascribe any meaning to the zero point of the tick counter."

If you want to know how much time elapsed since the application of power, you need to use a stopwatch.

Comments (45)
  1. Xv8 says:

    There are only 4 unsolved problems in programming.

    Naming Things, Cache Invalidation, Address Validation and Time.

    And the first 2 seem solvable.

  2. Joshua says:

    @Joker_vD: I just use the subtract between two returns of GetTickCount (I always know which one was earlier so integer rollover does the right thing!). Never had a problem where the 49.X days would come up because my distances were measured in seconds at worst.

  3. Gabe says:

    Who cares whether the tick count is from when the computer is turned on versus when Windows started running?

    I'm more interested in how the tick count is affected by sleeping and hibernating. If the system is asleep or hibernating for a day, does GetTickCount consider the system to have started a day later?

  4. Mark says:

    Joker_vD: you measure periodically at intervals of less than 20 days, and add them up.

  5. Innocent Bystander says:

    GetTickCount() is a massive pain due to the 49.7 day roll-over bugs that have plagued the universe since its inception. The windows debug setting should have been the standard build setting so that developers found these issues in development not production! How many testing shops use windows debug builds? How many run tests for > 50 days? Not many I'd wager, and so the fragility is passed on to the end user. More could have been done here to improve the reliability of all apps everywhere. Seriously, why was the 1 hour after start roll over not added as the default? (I know that there are ways to test this situation, but surely setting this as the default would have made this situation much less of an issue).

    [In 1983, nobody left their computer on 24 hours a day. 49 days was ridiculously long. Also: It's hard to do 64-bit math on a 16-bit machine. -Raymond]
  6. Innocent Bystander says:

    @Raymond  

    Perhaps no one ran a Windows type PC for 50 straight days in 1983, but by the early 90's they certainly did. There was still plenty of time to change this default or provide an easy way (and recommend it) for developers/testers to do so in order to test.

    @Ken Hagan

    How about a compromise? 24 hours. 24 hours is enough that most testing shops will pick it up, and infrequent enough that most users will at least not blame windows.

    [As noted by another commenter: Once you change the default, you break apps. "Don't upgrade to Windows 2.0. Apps crash after 24 hours." -Raymond]
  7. Evan says:

    I work for Contoso and we're having a problem I am hoping someone can help solve. If I start our program soon after boot, it crashes about an hour later, but after that it runs fine. Can anyone help?

  8. Innocent Bystander says:

    @Evan

    You likely have a bug involving Get­Tick­Count(). Allow me to point you to this relevant documentation ... on resolving your issue (or contact the software provider). At 50 days you don't get bug reports saying "this app crashes after 50 days" you get a variety of reports, none of them reproducible, saying: "the app randomly crashes every once in a blue moon".

  9. Anon says:

    @Evan

    If this is only on some machines, check to make sure no one has changed the default GetTickCount() value to just before the rollover on those machines in HKLMSoftwareMicrosoftWindows NTCurrentVersionThingsAddedByRequestOfInnocentBystanderGetTickCountValue. You shouldn't worry about this being a problem in  production, since that value doesn't normally roll over for 50 days, and no one runs a Windows machine for that long without rebooting.

    Signed,

    Highly Upvoted Anonymous StackOverflow Answer

  10. j b says:

    Regarding this huge rollover problem...

    I have come up with this great idea that could be useful: If t1 is immediately before the rollover, and t2 is shortly after, rather than doing a subtraction, we could add the distance from t1 to the rollover and t2 from the rollover. Since the rollover is at 000...0000, the second value is given directly by the t2 value, the problem is the t1-to-rollover. But we can obtain it by calculating 0-t1, which can be done by a neat little trick: Flip all bits over to their opposite values, and then adding 1 to the value (some people call this the "2-complement"). Using this little trick, the two distances from the rollover point is calculated by t2 + ( 0 - t1 ), which is equal to t2 - t1, if you use this "2-complement" trick. Neat, isn't it?

    Now then, how many instructions do you need for that? I really wonder how t2 - t1 would be handled by an ordinary simple subtraction instruction on a 2-complement machine. Maybe it wouldn't be THAT much different.... :-)

  11. Dan Bugglin says:

    There is a perfectly acceptable way to determine a time interval: poll the system clock. Unix timestamps won't roll over until 2038. If you use a mechanism which uses a 64-bit number by the time it's a problem you'll be long dead (.NET goes until the end of the year 9999).

    Sure it's not as accurate, but if you have to record a large enough time interval that tick count is overflowing, somehow I doubt the precision is that important. Not to mention it's likely the user will have rebooted long before it overflows, in most cases (Patch Tuesday is once a month, if nothing else). Having a server sorta invalidates that assumption but my "precision" argument still stands...

    Side note: I remember when my Windows XP tick count rolled over, once. That was when I knew for sure we had entered the era of the stable Windows OS.

  12. Andreas Rejbrand says:

    My main computer is always on, but in practice the uptime never exceeds a month or so, because of Windows Update demanding reboots.

  13. Aaron says:

    Other than a stop-watch, there must be a good way to measure computer boot-up speed.

    Boot speed has frequently been a competitive selling point for laptops, and measured down to the fraction of a second.

    From an electrical circuit point of view, its not hard to imagine a hardware clock that starts at zero, and counts as long as it has power, regardless of BIOS, POST, OS, drivers, or anything else.

    I think a 555 circuit is essentially this.

  14. Adam Rosenfield says:

    A certain piece of build automation software used at a company I used to work for had a bug in it where we'd get a build failure if a build happened to be running when GetTickCount() rolled over.  IIRC, the stack traces and debugging info from the error logs it produced indicated that a C# checked arithmetic exception was getting thrown a few instructions after a call to GetTickCount().

    I figured this out and predicted another build failure to the minute (2^32 seconds later) and wowed by coworkers with my psychic powers (although to be correct, a build had to be running at the time, and builds were probably running maybe 50% of the time during business hours).

  15. Joker_vD says:

    @The MAZZTer: About that "poll the system clock" — what about NTP?

  16. Joshua says:

    [Also: It's hard to do 64-bit math on a 16-bit machine. -Raymond]

    No harder than 32 bit math. The only "hard" op is division, and it's a library op even for 32 bit. The actual problem is long long int hadn't been invented yet.

  17. Mark says:

    Joshua: hard in the sense that you generally only have one 32-bit register to do stuff with (dx:ax) without saving stuff to the stack. If you have to add two 64-bit numbers, you're going to have to do that in memory. Not to mention the fact that the library for doing 64-bit division with 16-bit stores is not small.

    ]Mostly because no 16-bit compiler supports 64-bit integers. -Raymond]
  18. JJJ says:

    Even if the counter started counting the instant the computer received power, it still wouldn't be accurate.  Clocks drift and by the time it's ready to roll over at 50 days anyway it's going to be off by several seconds.

    Regarding automated boot-up time measurements, I would do it using an external PC, an ethernet-connected power switch, and an idle-time task program from yesterday.  The external PC powers on the computer and listens on a socket.  The idle-time task connects to the socket to signal that it's done booting.

  19. Kirby FC says:

    Although it is true that there is no way to determine how long it has been since the 'system' was started, In Windows 7 (and probably other versions as well) if you go into Task Manager and select the 'Processes' tab there is an item at the bottom titled 'Up Time'.  

    Mine currently says 0:09:30:04 so obviously someone on the Windows team feels it is necessary, and possible, to keep track of how long it has been since Windows started.

  20. Cesar says:

    @The MAZZTer: the system clock is not monotonic; it can go backwards, either because the user changed the system time or because a NTP daemon decided that the clock was wrong. It can also have large discontinuities for the same reason. What you want is a monotonic clock (CLOCK_MONOTONIC on Unix, I don't know the equivalent on Windows).

    @Raymond: actually, on UEFI it seems to be possible to know how long the firmware took to POST. On Linux, systemd-analyze can show you that information, as long as you use the correct bootloader; said bootloader also tells systemd how long it took within the bootloader itself.

    And on a related trivia, the Linux kernel also does the "start the time just before rollover" trick (starting at -300 seconds), for the same reasons. Only that the relevant timer (jiffies) is not exported to userspace, and there's no ABI guarantee for drivers on Linux, so there were no compatibility concerns and it's always done.

  21. xor88 says:

    The debug build trick seems to not have helped the CLR team. The .NET Framework is full of unsafe uses of TickCount (which I know from Reflector). Every 50 days all kinds of timeouts suddenly trigger. Really scary stuff, especially in server apps.

  22. Joker_vD says:

    By the way, sometimes you also want to compare TickCounts to detemine which event happened earlier. How does one then work around the "timer tick rollover bugs"? I can think only of two workarounds: either use GetTickCount64, or call GetTickCount() at the program start, and then use deltas based on that value (so that if you start an minute before the rollover, and an event happens two minutes after the rollover, you subtract (MAX_DWORD - 60*1000 + 1) from 120 * 1000 and get 180 * 1000, three minutes, which is the correct answer. But it still limits the window of events you can track as 50 days big, which sometimes can be limiting :(

    [Obviously, if you need to track durations longer than 50 days, you cannot use GetTickCount, because you have no way to tell the difference between a tick count of X and a tick count of X + 2^32. (And assuming you don't have to deal with intervals of more than 20 days, handling rollover is much easier than you describe.) -Raymond]
  23. foo says:

    Dealing with rollover and epoch tracking was ever-present in GPS land which has (or at least had) a 1024 week rollover for its timestamps. And the computers at the time ran Windows 3.1. Anyways for intervals using MSVC/Win32/x86 I've always just subtracted the finish - start if both variables are a DWORD and the result is a long. If the result is also a DWORD then do a labs() or cast a subtracted variable to a long.

  24. Ken Hagan says:

    "Seriously, why was the 1 hour after start roll over not added as the default?"

    I'm guessing, but I would guess that it wasn't the default *in 1983* because programmers were smarter then and it hasn't subsequently *become* the default because of the usual argument that Microsoft prefer to side with end-users rather than developers. (Once there are applications "out there" that mis-behave, the most helpful initial value for the ticker is the one that puts off the bug for as long as possible, rather than one that waits for you to get an hour's work done and then crashes.)

  25. Joshua says:

    Hmm my Windows uptime check was GetTickCount64. Obviously no good in checked builds but I never used it for anything important. I wonder what Task Manager uses. Start time of the System process should do it assuming you can see it (permissions).

  26. Azarien says:

    "Mostly because no 16-bit compiler supports 64-bit integers. -Raymond"

    Open Watcom does. But I doubt it did back in 1983..

  27. Innocent Bystander says:

    @xor88

    That is a depressing story. GetTickCount64 should sort these issues out eventually, but you would have thought that Microsoft could keep their own house in order. Perhaps they don't use the windows checked builds either.

    GetTickCount is an example of a method that will inevitably lead to issues as it can't be easily tested. The Windows api is full of such things. In magical unicorn land I'd like special test version of the windows api where I could inject errors for testing. Please.

  28. DebugErr says:

    Can I enable this 50 days - 1 hour debug setting in unchecked version of Windows too? I'd like to test my applications against it - and I'm also curious if other applications handle it correctly.

  29. Tomashu says:

    @DebugErr

    What about Application Verifier, it has option called "TimeRollOver".

  30. Brian_EE says:

    @Aaron - a 555 circuit doesn't count anything. It is simply a pulse generator. You actually need something more to do the counting.

  31. Brian_EE says:

    [Mostly because no 16-bit compiler supports 64-bit integers. -Raymond]

    Surely you meant 16-bit Windows compilers (of that era). Because my 16-bit MPS430 compiler surely supports 64-bit integers today.

    And by support, you meant natively supports. Because you could just make a struct of four 16-bit words (which in memory looks an awful lot like a native 64-bit number if you mind your endianess) and create your own library for simple operations on pairs of those structs.

    [Problem: "I want to measure how much time has elapsed between two events." Solution 1: "Wait 30 years, and then there will be a compiler that will support this." Solution 2: "Here's a structure, go do multiprecision arithmetic. (Good luck getting carry to work in C.)" -Raymond]
  32. laonianren says:

    @Joshua I guess Task Manager's "Up Time" comes from HKEY_PERFORMANCE_DATA's "SystemSystem Up Time" counter.  This shows the time the system has actually been awake since boot; the elapsed time of the System process (which is just the start time in disguise) also includes time spent asleep since boot.

  33. Joshua says:

    Solution 3: Library function in kernel.dll takes two structures in and spits double out.

    [You're telling me I have to link in a 30KB floating point emulator library just to calculate elapsed time? -Raymond]
  34. Joshua says:

    [You're telling me I have to link in a 30KB floating point emulator library just to calculate elapsed time? -Raymond]

    Every 16 bit Windows deployment I looked at would have consumed less RAM and disk if there was one master copy in the system libraries.

    [Okay, then, "Why is Windows using 10% of my RAM for floating point emulation?" -Raymond]
  35. Ben Voigt says:

    @xor88: The debug build trick didn't help the CLR because the test team for .NET evidently doesn't use debug or checked builds.  I installed a checked build once, with a kernel debugger attached, and WinForms was triggering "invalid argument" type messages from the enhanced system API argument validation constantly.  I reported some on Connect, nobody cared that Reflector showed the code was really and truly violating MSDN requirements; they fixated on can't repro, because their repro environment didn't have checked builds.

    see connect.microsoft.com/.../306816

  36. Ben Voigt says:

    Oh by the way, to tie back into Raymond's blog, WM_PAINT handling in both scratch programs (C and C++ versions) has that bug.

    By now there's probably an AppCompat shim for "illegally calls BeginPaint with an empty update region"

    [Not sure what you're talking about. And besides, it's legal to call BeginPaint even when the update region is empty. Indeed, how do you even know whether the update region is empty except by calling BeginPaint and then checking the rectangle it returns? -Raymond]
  37. Joshua says:

    Raymond: The problem is not checking for BeginPaint returning NULL.

    [Then the code operates on a null DC, which paints nothing. Lots of invalid parameter errors, but the failure mode is safe. -Raymond]
  38. cheong00 says:

    Actually 8087 did offer 64-bit integer addition. Just that most systems didn't equip with x87 coprocessor and you can't be sure about it's existence before 486 series of CPUs gone out of market. (And it's not quite enough if you count the non-widely-used Nx586 in to support pool)

  39. ZLB says:

    Surely GetTickCount64() solves any issues that may be cause by wrap-araound and uptimes of more than 51 days?

    Ok, it's still a problem for older software or if you need to support WinXP still though, but for new software, it shouldn't still be a problem.

  40. Marc K says:

    I don't understand all the drama over the 50 day rollover. Common practice has been to subtract two values from GetTickCount() using DWORDs (unsigned 32 bit ints). The underflow will ensure you get the correct delta. The problem with the rollover comes into play if you try to do comparisons, which you shouldn't be doing. (e.g. if (currentValue < lastValue) LaunchNuclearStrike();)

    [You'd be surprised how many people are not familiar with the common practice. Even in this thread, people suggest alternatives. -Raymond]
  41. Jim Mischel says:

    If you want to test your programs against the rollover problem, there's no need to get the debug build that starts the clock at rollover - 300 seconds. In C, simply redefine GetTickCount() in your debug builds so that it goes to your own function, which gets the tick count and adds (UINTMAX-300000) to it. You can do similar things in other languages.

    [Don't forget to inject code into your TIMERPROCs and window hooks to adjust the time there. And redefine functions like GetMessage (which accept or return timestamps). Uh-oh, but you don't control all callers of GetMessage. And if you need to marshal that timestamp to another process... -Raymond]
  42. cheong00 says:

    Marc K: Relying on underflow isn't good either. wait until the clock ticks run over 99 days.

    (Hint: My boss in summer job have had Novell servers that aren't rebooted over 5 years, since the age of HKT, and still in good condition at that time.

  43. Ben Voigt says:

    Raymond, that's not what MSDN documentation for WM_PAINT (msdn.microsoft.com/.../dd145213%28v=vs.85%29.aspx) says:

    "An application should call the GetUpdateRect function to determine whether the window has an update region. If GetUpdateRect returns zero, the application should not call the BeginPaint and EndPaint functions."

    Perhaps it is *legal* to do things that MSDN says "should not", but the resulting failure cascade is not the result I like to have from the framework I use to build applications.

    [This is in a paragraph that is providing guidance on handling RDW_INTERNALPAINT; not guidance on BeginPaint in general. I'll see what I can do to have the documentation clarified. -Raymond]
  44. Ben Voigt says:

    @Raymond: Besides, I don't think it is safe for the .NET Framework to assume that an application never calls RedrawWindow with RDW_INTERNALPAINT.  Of course, the assumption IS valid for your scratch program.

  45. Ben Voigt says:

    @Raymond: My interpretation of the documentation was:

    1. WM_PAINT can arrive even though the update region is empty.

    2. RDW_INTERNALPAINT is one of the things that sends WM_PAINT even when the update region is empty.

    3. To make your WM_PAINT handler work properly when the update region is empty, call GetUpdateRect first and only call BeginPaint if the update region is not empty.

    Specifically, I interpret that you are supposed to skip BeginPaint *any* time the update region is empty, whether or not RDW_INTERNALPAINT was involved.

    [The documentation is misusing the word "should" (how timely given a topic a few days later. The "should" here is saying "If you want to avoid null updates, you should..." The discussion is in the context of detecting null update regions. -Raymond]

Comments are closed.

Skip to main content