9

I've been reading tips about Javascript performance boosting and one tip said to cache all the variables (that don't change) in the loops comparer statement and I was wondering if this also applied to .NET.

Assuming I had a simple for loop, which one of the following would be faster or would they be the same?

No Cache:

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

}

With Cache:

for (int i = 0, count = someArray.Length; i < count; i++)
{

}

According to the article "caching" the value of Length cuts out one operation in the loop because its faster to access local variables compared to accessing members of an object. Is it actually faster declaring a local variable compared to simply accessing the member? Does the compiler pick up on this and automatically cache the value? Is there any negatives in declaring a local variable over accessing the member?

Whilst speed is probably a key factor here, its not the only one. My next question would probably be which one is more efficient. Which uses less memory allocations? Which performs less stack manipulation? etc...

From comments, it seems accessing array lengths is pretty fast. Lets say I use an IList<> instead. Would caching the value of Count be faster than retrieving it each iteration?

jduncanator
  • 2,154
  • 1
  • 22
  • 39
  • 3
    Worth to read before: [The Sad Tragedy of Micro-Optimization Theater](http://www.codinghorror.com/blog/2009/01/the-sad-tragedy-of-micro-optimization-theater.html) – Steve May 19 '13 at 07:33
  • 2
    @Steve I know wasting time on Micro-Optimization is time wasted, however this is more of a curiosity rather than a major performance optimization discovery. However, it could also possibly move from a micro-optimization classification to quite a large performance optimization if you are iterating over an array of 1,000,000+ items! However that article is a good read :) Thanks! :P – jduncanator May 19 '13 at 07:35
  • No judgement by me on your intentions, they are pretty clear. Just keeping the things in perspective. I am just looking now to the IL code generated by your instructions and the second loop seems not optimized at all, but this could be easily changed on the jitted code. – Steve May 19 '13 at 07:44
  • .net compilers are smart..the compiler would automatically optimize code wherever needed..you dont need to worry about it..focus on your app logic **not** optimization..atleast in .net – Anirudha May 19 '13 at 07:46
  • 4
    @jduncanator: Just because you're iterating over a million items doesn't mean it would become important (even if it were a benefit at all). It depends on the cost of iteration vs the cost of what's in the body of the loop. Saving a millisecond due to micro-optimization is great if the total time for the whole loop is 3ms. Not so much if it's an hour. – Jon Skeet May 19 '13 at 07:50
  • @JonSkeet True, but what if the `Length` property was changed for say, an `IList<>`'s `Count` property? Is it still just as fast? – jduncanator May 19 '13 at 07:52
  • @jduncanator: Well I'd normally use a `foreach` loop anyway to be honest. But even if not, the loop would have to do very little in its body, and this would have to be an important part of the overall system performance for me to decide to write the more complicated code in the name of performance. Definitely not something to do before *proving* it's worthwhile in the specific case. – Jon Skeet May 19 '13 at 07:54
  • I need to fix my previous assertion, the second loop, apart from the initial setup, is two opcodes shorter than the first one. – Steve May 19 '13 at 07:57
  • IIRC, this particular 'optimization' was actually a de-optimization with the very first C# compiler. Because the optimizer didn't recognize it as a normal for-loop. Moral: keep it simple and always verify any 'optimization'. – H H May 19 '13 at 08:21
  • @HenkHolterman Thats what I thought. So the compiler recognizes this "form" now? I never really do anything without proper testing, so thats why I asked for some expert opinion on SO :) – jduncanator May 19 '13 at 08:23
  • You'd have to measure. It also depends on the code inside the loop. And I should have said 'Jitter' instead of 'C# compiler'. – H H May 19 '13 at 08:26

4 Answers4

3

Just to provide a different answer. Caching the length value may help you in some particular cases.

In my case, we were using a sample code with selenium for ui tests. The code looked like this:

for (int i = 0; i < Grid.VisibleRows.Length; i++) {
    // do something
}

Our grid was just a class representing an html table. The VisibleRows was getting a IWebElement[] and then using Length property. Each iteration implied to go the UI and get all rows.

Of course another implementation might be to just go to the UI once and get the length (instead of counting the rows in memory), but still moving the code out of the for loop implied an improvement on performance - I reduced the number of roundtrips to the UI from many to just one.

So code now looks like this:

var rowsLength = Grid.VisibleRows.Length;
 for (int i = 0; i < rowsLength ; i++) {
        // do something
    }

For us, the manual caching worked out. Using a stopwatch we checked that we were able to reduce the time of that part of code from 32993 ms to 12020 MS

Gonzalo.-
  • 12,512
  • 5
  • 50
  • 82
1

I once tried caching vs. array.Length. Array.Length was faster. I think, thats because of inner structure of virtual machine and "safety". Whe you use .Length notation, it consider, that it will never overflow array. With variable, it is unknown and it makes additional tests. Assembly code looks one way, but inner behavior of virtual machine is other thing.

But on the other hand, you are doing premature optimalization.

Martin Perry
  • 9,232
  • 8
  • 46
  • 114
0

In a compiled language, all you're doing is premature optimization. I suppose an interpreted language might save a bit, but even there it feels like your payoff would be so minimal for what is (in my experience) an unusual way to code a for loop.

To answer your question directly for C#, no, the compiler does not optimize anything by caching. I could very easily create a new array with a new length during the loop. As such, it will load the array length every time it evaluates the stop condition. Or worse, I may not be using "traditional" style stop conditions and may need to evaluate a function to know to stop.

That said, here's a simple program:

static void Main( string[] args ) {
    int[] integers = new int[] { 1, 2, 3, 4, 5 };

    for( int i = 0; i < integers.Length; i++ ) {
        Console.WriteLine( i );
    }
}

And here's the IL (with nops removed):

IL_000d:  call       void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array,
                                                                                                     valuetype [mscorlib]System.RuntimeFieldHandle)
IL_0012:  stloc.0
IL_0013:  ldc.i4.0
IL_0014:  stloc.1
IL_0015:  br.s       IL_0024
IL_0018:  ldloc.1
IL_0019:  call       void [mscorlib]System.Console::WriteLine(int32)
IL_0020:  ldloc.1
IL_0021:  ldc.i4.1
IL_0022:  add
IL_0023:  stloc.1
IL_0024:  ldloc.1
IL_0025:  ldloc.0
IL_0026:  ldlen
IL_0027:  conv.i4
IL_0028:  clt
IL_002a:  stloc.2
IL_002b:  ldloc.2
IL_002c:  brtrue.s   IL_0017

The key answer to your question here, is it is pushing the array to location 0 in the stack, then during IL_0026 is performing a call to get the length of the array, then IL_0028 is performing the less than comparison, and finally going to IL_0017 if the evaluation is true.

By caching the length of the array, all you're saving is a ldlen and a stloc call. The ldlen instruction should be fast as getting the length of an array is not much of time waster.

EDIT:

The primary difference with a list will be this instruction:

IL_002b:  callvirt   instance int32 class [mscorlib]System.Collections.Generic.List`1<int32>::get_Count()

callvirt will take up more time, but all this function realistically does is return a private variable.

You'd be way better off worrying about things that take milliseconds - like chunking database calls, or optimizing SQL queries so they're faster, etc, than trying to shave off single IL operations.

Community
  • 1
  • 1
Adam Sills
  • 16,896
  • 6
  • 51
  • 56
  • Makes sence, but would this still apply to say, an `IList<>`? – jduncanator May 19 '13 at 07:49
  • 10
    Note that the IL doesn't tell the whole story anyway. I'd expect the JIT to be spot this incredibly common pattern and optimize it further for the array case, if it can tell that the array variable doesn't change (to refer to a different array) during the loop. – Jon Skeet May 19 '13 at 07:55
  • Not that it matters in this particular instance, but is the IL you provide above compiled with optimisations disabled? Current C# compiler optimisations transform the `clt` and `brtrue` into a `blt` from what I can see. – Bob Dec 16 '14 at 06:25
  • While it true caching `Array.Length` has very little impact on performance, on other properties like `List.Count` it's another story. – tigrou Jul 18 '21 at 21:49
-1

I doubt it will make your code faster, and may actually make it slower. Getting the length of an array is really cheap (probably an L1 hit), and computing the length beforehand may mess up the JIT's bound-check elimination.

In C#, writing: array[i] actually is more like writing: if (i >= array.Length) throw...; array[i]

The CLR writers spent alot of time making the JIT really good at eliminating those checks when unnecessary. Writing your loops in the second style may make the CLR unable to eliminate the checks.

mattsills
  • 264
  • 2
  • 5