A faster way of implementing periodic actions in loops (plus: the BCL Stopwatch class for measuring performance)
Does this look familiar?
for (int i = 0; i < 100000; i++)
{
// Do whatever.
if ((i % 100) == 99)
// every 100 cycles, do something special (eg display a progress update)
}
The problem with this is that (i % 100) is a very slow calculation for a computer that doesn’t think in base 10 (interestingly, (i % 128) is much faster). An obvious improvement would be to use another loop variable that we reset at the end of each period:
for (int i = 0, j = 1; i < 100000; i++, j++)
{
if (j == 100)
{
// This will fire on i == 99, 199, 299, ...
j = 0;
}
}
But if you are willing to be a little flexible on the period length then there is another way that is faster than both of these approaches and which does not require an extra variable: bitwise AND.
The trick is that if the period length is a power of two (ie period length = 2n) then each and every combination of n bits appears exactly once in each period. Simply checking that the last n bits are all 0 will alert you to the end of each period – and bitwise comparison is just about the most elementary (and therefore fastest) thing a computer can do.
for (int i = 0; i < 100000; i++)
{
// Do whatever.
if ((i & 127) == 0)
// This will fire on i == 0, 128, 256, 384, ...
}
In this example, ((i & 127) == 0) is functionally equivalent to (but still faster than) ((i % 128) == 0). Obviously you can test for ((i & 127) == 127) if you want to fire on 127, 255, …
So, to summarize:
- First, see if you can use a period length that is a power of two so that you can use the bitwise test. It is particularly handy for periods of length two (ie firing on every other iteration) – use ((i & 1) == 0) rather than ((i % 2) == 0).
- If your period length is not a power of two, use an extra variable to count the periods.
- Don’t use (i % nn) on a big loop – it’s hideously slow.
The Stopwatch class
If you want to measure the performance of these techniques, or anything else for that matter, the System.Diagnostics.Stopwatch class is very handy. One of its great features is that it will use the hardware-based performance counter if one is available (in my experience, it almost always is). The hardware performance counter will give you precision of a few microseconds, whereas the DateTime.Tick property (based on the system timer) is accurate only to about 15ms, which isn’t precise enough for short events. Measuring an event is easy:
Stopwatch timer = Stopwatch.StartNew();
DoTheThingThatYouAreMeasuring();
timer.Stop();
Console.WriteLine(String.Format("It took {0:0.00} milliseconds.", timer.Elapsed.TotalMilliseconds));
Note the call to timer.Stop(). Without this, the clock is still ticking between the end of function call and the evaluation of the Elapsed property, which would add a microsecond or two to the reported time. I’d say that TotalMilliseconds should be accurate to two decimal places if you are using a high-res timer (check the Stopwatch.IsHighResolution property)
Carl blogging! Awesome. Nice one too.