Beware of the stopwatch

来源:互联网 发布:linux mv到当前目录 编辑:程序博客网 时间:2024/05/16 05:50

In a comment on my last post, craniac argued that the StopWatch-class should be used for measuring time intervals.

Sadly, measuring a time interval is not as easy as it sounds. There are several mechanisms, I will explain 3 of them and point out their strengths and weaknesses.

1. DateTime.UtcNow

   1: DateTime begin = DateTime.UtcNow;
   2:  
   3: ...
   4:  
   5: DateTime end = DateTime.UtcNow;
   6: Console.WriteLine("Measured time: " + (end-begin).TotalMilliseconds + " ms.");

This is the fastest mechanism because under the hood it only reads a counter from memory (the code above takes only 76 nanoseconds to execute on my machine). However the resolution is not so great: only 10 milliseconds on recent version of Windows. If you want to measure a piece of code that takes shorter to execute, you will have to execute it e.g. 1000 times and measure how long this takes, then divide the result by 1000.

Another disadvantage is that this is not reliable if the system-time changes. This should be a very rare situation, but could for instance happen through synchronization with a time server.

2. Stopwatch

   1: Stopwatch watch = new Stopwatch();
   2: watch.Start();
   3:  
   4: ...
   5:  
   6: watch.Stop();
   7: Console.WriteLine("Measured time: " + watch.Elapsed.TotalMilliseconds + " ms.");

This mechanism is a bit slower than the previous one (10 times slower on my machine) so for short intervals this may have an impact on the result. However there are some serious issues:

  • This can be unreliable on a PC with multiple processors. Due to a bug in the BIOS, Start() and Stop() must be executed on the same processor to get a correct result.
  • This is unreliable on processors that do not have a constant clock speed (most processors can reduce the clock speed to conserve energy). This is explained in detail here.

I suspect that you can get a reliable result if you run it on a single-processor machine and disable any power-saving options in the BIOS. I haven’t tested this though.

On the upside, it has the hightest possible resolution (which depends on the hardware it runs on).

3. Process.TotalProcessorTime

   1: TimeSpan begin = Process.GetCurrentProcess().TotalProcessorTime;
   2:  
   3: ...
   4:  
   5: TimeSpan end = Process.GetCurrentProcess().TotalProcessorTime;
   6: Console.WriteLine("Measured time: " + (end - begin).TotalMilliseconds + " ms.");

This mechanism is different from the previous ones because it does not measure how much time has passed, but it measures how long your process has kept the CPU busy. This is great for performance measurements:

  • The timings are not distorted by other processes that consume a lot of CPU.
  • You can measure the impact that your code has on the overall performance of the system. On laptops, this also gives an indication towards the battery-power that is consumed by your process. This can be important for applications that run for a long time (such as services and other background tasks).

To interpret the measured time correctly, you should realize that time that is spent while your code is waiting (e.g. in a Sleep) will not be counted. On the other hand, if your process is keeping multiple processors busy, the time of each processor will be added (if a dual-core processor is kept 100% busy, the ‘TotalProcessorTime’ will increment with 2 each second!).

Note that this mechanism has the worst performance: the code above takes 19264 nanoseconds on my PC. This is 250 times slower than using DateTime.UtcNow!

So there is not one-size-fits-all solution to measure a time interval. Personally, if I want to measure how long a piece of code takes to execute, I run it in a loop (e.g. one million times) and measure it using DateTime.UtcNow.

0 0