Elapsed Time with Environment.TickCount() - Avoiding the wrap

12,749

Solution 1

Avoiding the wrapping problem is easy, provided that the time span you want to measure is no more than 24.8 days (anything longer can't be represented in a signed integer). In C#:

int start = Environment.TickCount;
DoLongRunningOperation();
int elapsedTime = Environment.TickCount - start;

I am not very familiar with VB.NET, but as long as you use unchecked math, this will work and you don't have to worry about the wrap.

For example, if Environment.TickCount wraps from 2147483600 to -2147483596, it doesn't matter. You can still compute the difference between them, which is 100 milliseconds.

Solution 2

Since I was looking into this and stumbled onto this page I'll share a small benchmark I made.

When running the code ignore the first 1000-result from each test (used for warmup). Also ignore the number printed after "ignore:". Its just there just to ensure no compiler optimization is done that would affect the result.

Results on my computer:

Test1 1000000: 7ms    - Environment.TickCount
Test2 1000000: 1392ms - DateTime.Now.Ticks
Test3 1000000: 933ms  - Stopwatch.ElapsedMilliseconds

Both DateTime.Now.Ticks (test 2) and Stopwatch.ElapsedMilliseconds (test 3) are considerably slower than Environment.TickCount (test 1), but not enough to be noticeable unless you are performing a lot of calculations. For example I was investigating this because I need a cheap way of getting time in tight game loops.

I think my solution will have to be something like (untested):

var elapsed = Environment.TickCount - start;
if (elapsed < 0)
   elapsed = Int32.MaxValue - start + Environment.TickCount;

Benchmark code:

void Main()
{
    Test1(1000);
    Test1(1000000);
    Test2(1000);
    Test2(1000000);
    Test3(1000);
    Test3(1000000);
}

void Test1(int c) {
    var sw = new Stopwatch();
    sw.Start();
    long sum = 0;
    for (var i = 0; i < c; i++)
    {
        sum += Environment.TickCount;
    }
    sw.Stop();
    Console.WriteLine("Test1 " + c + ": " + sw.ElapsedMilliseconds + "ms   (ignore: " + sum + ")");}

void Test2(int c) {
    var sw = new Stopwatch();
    sw.Start();
    long sum = 0;
    for (var i = 0; i < c; i++)
    {
        sum += DateTime.Now.Ticks;
    }
    sw.Stop();
    Console.WriteLine("Test2 " + c + ": " + sw.ElapsedMilliseconds + "ms   (ignore: " + sum + ")");
}
void Test3(int c) {
    var sw = new Stopwatch();
    sw.Start();
    long sum = 0;
    var stopwatch = new Stopwatch();
    stopwatch.Start();
    for (var i = 0; i < c; i++)
    {
        sum += stopwatch.ElapsedMilliseconds;
    }
    sw.Stop();
    Console.WriteLine("Test3 " + c + ": " + sw.ElapsedMilliseconds + "ms   (ignore: " + sum + ")");
}

Solution 3

Just use the stopwatch:

Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
Thread.Sleep(10000);
stopWatch.Stop();
// Get the elapsed time as a TimeSpan value.
TimeSpan ts = stopWatch.Elapsed;

It will return an int64 and not an int32. Plus it's easier to understand.

Solution 4

I'm not sure what you're trying to accomplish. It looks like you are trying to detect if a particular interval has occured and if so execute some specific logic.

This is likely to cause you no end of pain if you use Environment.TickCount for this purpose. As you pointed out this value can and will wrap roughly every 25 days. There is no way to prevent this from happening and if you try to you'll end up with bugs in your code.

Instead why not do the following:

  • Use an actual Timer for the internal and execution of the event
  • Store the StartTime as a DateTime (or just Date in VB) value. This value has a much longer range. It's highly unlikely that your app will run long enough for this value to wrap around(OS will need a reboot long before then :) ).
Share:
12,749
user79755
Author by

user79755

Updated on June 04, 2022

Comments

  • user79755
    user79755 almost 2 years

    Does the absolute value protect the following code from the Environment.TickCount wrap?

    If Math.Abs((Environment.TickCount And Int32.MaxValue) - StartTime) >= Interval Then
        StartTime = Environment.TickCount And Int32.MaxValue ' set up next interval
        ...
        ...
        ...
    End If
    

    Is there a better method of guarding against the Environment.TickCount wrap-around?

    (This is .NET 1.1.)

    Edit - Modified code according to Microsoft Environment.TickCount help.

  • usr
    usr almost 12 years
    Still wrapping after 50 days.
  • usr
    usr almost 12 years
    Using this method you can track 50 days at most.
  • eselk
    eselk almost 12 years
    I know this is 3 years old, but Stopwatch/QueryPerformanceCounter is dead IMO because almost all PCs have multiple processors now, and it isn't reliable in that case unless you force your thread to run on one process.. but then that probably invalidates what you wanted to test for anyway since your release version will probably not have that restriction.
  • Patrick Allwood
    Patrick Allwood over 10 years
    Yes I know this was asked years ago, but, if you need to track more than 50 days using this method, why not keep tabs on how many times the counter has wrapped. Then you can run it for 50 * Int32.MaxValue days. Poll to see if it has wrapped every so often and keep track.
  • Karsten
    Karsten almost 7 years
    @eselk this is not true for all versions of windows above Windows XP. See here.
  • Philm
    Philm almost 7 years
    I misunderstood the comment from Karsten: More clear: you can safely use and it is recommended to use Stopwatch() for Windows XP AND above !! According to that linked MSDN article by Karsten there are only few processors where Performancecounters (and Stopwatch) takes a long time: "On a relatively small number of platforms that can't use the TSC register as the QPC basis, [..]., acquiring high resolution time stamps can be significantly more expensive than acquiring time stamps with lower resolution. If resolution of 10 to 16 milliseconds is sufficient, you can use GetTickCount64...."
  • ygoe
    ygoe about 5 years
    unchecked doesn't seem to be necessary for the cast to work as expected. It wraps automatically.