4

I've read that array-bounds checking can slow down hot spots in programs because with each access of the array, an additional instruction must be executed to ensure it is within the bounds of the array.

As a result, sometimes people recommend letting the upper-bound of the for loop be array.Length because the JIT can see it and optimize out the array-bounds checking (assuming the body only accesses the current loop variable each iteration.

However, this conflicts with the idea to save the length before-hand in a separate int variable as a form of loop hoisting in to an invariant that many recommend to save performance as well.

Assuming the code will be run in release and not from within Visual Studio (so no debugger), which is optimal? Hoisting the upper-bound in to a separate variable or maintaining the array.Length for the JIT hints? Is array-bounds checking even still being performed in this case?

2 Answers2

5

Tim is correct -- this is premature optimization, and it's very likely that you have far better things you can optimize (use a profiler!)

To answer your question, however, the JIT compiler will elide bounds checks in many cases.

http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds-check-elimination-in-the-clr.aspx

Simple cases The good news is that we do eliminate bounds checks for what we believe to be the most common form of array accesses: accesses that occur within a loop over all the indices of the loop. So, there is no dynamic range check for the array access in this program:

static void Test_SimpleAscend(int[] a) {

    for (int i = 0; i < a.Length; i++)

        a[i] = i;  // We get this.

}

As of that writing, hoisting a.Length into a separate variable prevents the x86 JIT compiler from doing the bounds check

No excuses here: there’s no reason not to get this, the JIT compiler ought to know that n is the length of the newly allocated array in ia. In fact, the author of such code might think he was doing the JIT compiler a favor, since comparison with a local variable n might seem cheaper than comparison with ia.Length (though this isn’t really true on Intel machines). But in our system, at least today, this sort of transformation is counterproductive for the x86 JIT, since it prevents the bounds check from being eliminated. We may well extend our compiler(s) to track this sort of value equivalence in the future. For now, though, you should follow this piece of practical advice: · Advice 2: When possible, use “a.Length” to bound a loop whose index variable is used to index into “a”.

Mark Sowul
  • 10,244
  • 1
  • 45
  • 51
  • Note that the descending version ( for (int i = a.Length - 1; i >= 0; i--) ) does not get the eliminated bounds check, as of that blog post – Mark Sowul Mar 26 '15 at 01:26
1

First of all, this is an absolute classic case of pre-mature optimization. It is almost certainly something that is optimized away by the compiler, and unless you are actually measurable performance slowdowns never start looking at low level optimizations like this.

Second, if you want to actually know because you actually are seeing slowdowns, write both, compile, and run benchmark tests. Go a step further, and look at the compiled IL to see if it even makes a difference. But this should absolutely only be done if you are experiencing slowdowns. Even if it is faster by a miniscule amount (which I doubt), you are heavily trading a little performance for maintainability. And that is not a good tradeoff in the long return.

Tim
  • 2,878
  • 1
  • 14
  • 19
  • Excellently state, but one nitpick: the IL is not enough because the JIT compiler is where a lot of the magic lies – Mark Sowul Mar 26 '15 at 01:31
  • good point. Just looking at the IL isn't enough to state it is better. However, it is enough to state it is the same. The same IL will run at the same efficiency in the JIT compiler. So if you get the same result, you aren't making a bit of difference. – Tim Aug 16 '16 at 13:51