Should I save the length of the array to a local variable in C #
Very often I notice that people write like this:
They write this in the hope of speeding up the loop, thinking that creating a local variable eliminates the need for the CLR to call the getter for Array.Length each time. I decided once and for all for myself to understand it’s worth doing this or you can save your time and write without a temporary variable.
First, consider two methods in C #:
And let's look at the code that is obtained after JIT compilation in the release on .NET Framework 4.7.2 under LegacyJIT-x86:
It is easy to see that the number of assembler instructions is exactly the same - 15 pieces. And the logic of these instructions also almost completely coincides. There is only a slight difference in the order of initialization of the variables and the comparison of whether to continue the cycle. It can be noted that in both cases the length of the array is entered into registers two times before the cycle:
It turns out that both of the above methods are compiled into the same code, but the first one is written faster and there is no clear gain in execution speed.
The above assembler code led me to some thoughts and I decided to check out a couple more methods:
Now the current element and the length of the array are summed, only in the first case the length of the array is requested each time, and in the second it is stored once in a local variable. Let's look at the assembler code of these methods:
The number of instructions in these two methods and the instructions themselves are almost exactly the same, again there is a difference only in the order of initializing the variables and checking for a continuation of the cycle. Moreover, it can be noted that only the array is taken into account in calculating the sum.Length, which was taken the very first. Obviously, this is:
It is possible to draw the first conclusion that it is a waste of time to start an additional variable in order to try to speed up the execution of a cycle. the compiler will do everything for us. Setting a variable with a cycle length makes sense only to increase the readability of the code.
A completely different situation with ForEach. Take three methods:
And look at their code after JIT:
The first thing that catches your eye is that there are fewer assembler instructions compared to the for loop (for example, 12 instructions came out in the foreach for simple summation of elements, in for 15).
In fact, if you write a benchmark for vs foreach on 1,000,000 elements of an array, you get this picture for
and for
ForEach for arrays works faster than For. Why it happens? To understand this, you need to compare the code after JIT.
Let's look at ForEachWithoutLength. In it, the length of the array is requested only once and there are no checks on the output of elements outside the array. This happens because the ForEach cycle firstly prohibits changing the collection inside the cycle, and secondly, it will definitely not go beyond the collection. Because of this, the JIT can afford to generally remove the check for output from the array.
Now let's take a close look at the ForEachWithLengthWithoutLocalVariable method. In his code, there is only one strange moment in that sum + = length occurs not with the local variable arrayLength stored previously, but with the new one that the program asks every time from memory. It turns out that the memory accesses for the array length will be N + 1, where N is the length of the array.
It remains to consider the method ForEachWithLengthWithLocalVariable. Its code is no different from ForEachWithLengthWithoutLocalVariable, except for working with a long array. The compiler again generated the local variable arrayLength, with which it checks that the array is not empty, but the compiler honestly preserved our explicit local variable length and addition in the body of the loop already happens to it. It turns out that this method only accesses memory twice to determine the length of the array. This difference in the number of memory accesses in real applications is very difficult to notice.
The assembler code of the methods examined was so simple because the methods themselves are simple. If there were more parameters in the method, there would already be work with the stack, the variables would not only be stored in registers and perhaps there would be more checks, but the basic logic would remain the same: introducing a local variable with an array length can make sense only to increase the code readability . In addition, it turned out that Foreach in an array is often faster than For.
var length = array.Length;
for (int i = 0; i < length; i++) {
//do smth
}
They write this in the hope of speeding up the loop, thinking that creating a local variable eliminates the need for the CLR to call the getter for Array.Length each time. I decided once and for all for myself to understand it’s worth doing this or you can save your time and write without a temporary variable.
First, consider two methods in C #:
publicintWithoutVariable() {
int sum = 0;
for (int i = 0; i < array.Length; i++) {
sum += array[i];
}
return sum;
}
publicintWithVariable() {
int sum = 0;
int length = array.Length;
for (int i = 0; i < length; i++) {
sum += array[i];
}
return sum;
}
And let's look at the code that is obtained after JIT compilation in the release on .NET Framework 4.7.2 under LegacyJIT-x86:
WithoutVariable () ; int sum = 0; xor edi , edi ; int i = 0; xor esi , esi ; int [] localRefToArray = this.array; mov edx , dword ptr [ ecx + 4 ] ; int arrayLength = localRefToArray.Length; mov ecx , dword ptr [ edx + 4 ] ; if (arrayLength == 0) return sum; test ecx , ecx jle exit ; int arrayLength2 = localRefToArray.Length; mov eax, dword ptr [ edx + 4 ] ; if (i> = arrayLength2) ; throw new IndexOutOfRangeException (); loop : cmp esi , eax jae 056e2d31 ; sum + = localRefToArray [i]; add edi , dword ptr [ edx + esi * 4 + 8 ] ; i ++; inc esi ; if (i <arrayLength) goto loop cmp ecx , esi jg loop ; return sum; exit : mov eax , edi | WithVariable () ; int sum = 0; xor esi , esi ; int [] localRefToArray = this.array; mov edx , dword ptr [ ecx + 4 ] ; int arrayLength = localRefToArray.Length; mov edi , dword ptr [ edx + 4 ] ; int i = 0; xor eax , eax ; if (arrayLength == 0) return sum; test edi , edi jle exit ; int arrayLength2 = localRefToArray.Length; mov ecx , dword ptr [ edx + 4 ] ; if (i> = arrayLength2) ; throw new IndexOutOfRangeException (); loop : cmp eax , ecx jae 05902d31 ; sum + = localRefToArray [i]; add esi , dword ptr [ edx + eax * 4 + 8 ] ; i ++; inc eax ; if (i <arrayLength) goto loop cmp eax , edi jl loop ; return sum; exit : mov eax, esi |
Screenshot comparison in Meld

It is easy to see that the number of assembler instructions is exactly the same - 15 pieces. And the logic of these instructions also almost completely coincides. There is only a slight difference in the order of initialization of the variables and the comparison of whether to continue the cycle. It can be noted that in both cases the length of the array is entered into registers two times before the cycle:
- to check for 0 ( arrayLength );
- and a temporary variable to check in the loop condition ( arrayLength2 );
It turns out that both of the above methods are compiled into the same code, but the first one is written faster and there is no clear gain in execution speed.
The above assembler code led me to some thoughts and I decided to check out a couple more methods:
publicintWithoutVariable() {
int sum = 0;
for(int i = 0; i < array.Length; i++) {
sum += array[i] + array.Length;
}
return sum;
}
publicintWithVariable() {
int sum = 0;
int length = array.Length;
for(int i = 0; i < length; i++) {
sum += array[i] + length;
}
return sum;
}
Now the current element and the length of the array are summed, only in the first case the length of the array is requested each time, and in the second it is stored once in a local variable. Let's look at the assembler code of these methods:
WithoutVariable () int sum = 0 ; xor edi , edi int i = 0 ; xor esi , esi int [ ] localRefToArray = this . array ; mov edx , dword ptr [ ecx + 4 ] int arrayLength = localRefToArray . Length ; mov ebx , dword ptr [ edx + 4 ] if (arrayLength == 0 ) return sum ; test ebx , ebx jle exit int arrayLength2 = localRefToArray . Length ; mov ecx , dword ptr [ edx + 4 ] if ( i> = arrayLength2 ) throw new IndexOutOfRangeException ( ) ; loop : cmp esi , ecx jae 05562d39 int t = array [ i ] ; mov eax , dword ptr [ edx + esi * 4 + 8 ] t + = sum ; add eax , edi t + = arrayLength ; add eax , ebx sum = t ; mov edi , eax i ++ ; inc esi if ( i <arrayLength ) goto loop cmp ebx , esi jg loop return sum ; exit : mov eax , edi | WithVariable () int sum = 0 ; xor esi , esi int [ ] localRefToArray = this . array ; mov edx , dword ptr [ ecx + 4 ] int arrayLength = localRefToArray . Length ; mov ebx , dword ptr [ edx + 4 ] int i = 0 ; xor ecx , ecx if (arrayLength == 0 ) ( return sum ;) test ebx , ebx jle exit int arrayLength2 = localRefToArray . Length ; mov edi , dword ptr [ edx + 4 ] if ( i> = arrayLength2 ) throw new IndexOutOfRangeException ( ) ; loop : cmp ecx , edi jae 04b12d39 int t = array [ i ]; mov eax , dword ptr [ edx + ecx * 4 + 8 ] t + = sum ; add eax , esi t + = arrayLength ; add eax , ebx sum = t ; mov esi , eax i ++ ; inc ecx if ( i <arrayLength ) goto loop cmp ecx , ebx jl loop return sum ; exit : mov eax , esi |
Screenshot comparison in Meld

The number of instructions in these two methods and the instructions themselves are almost exactly the same, again there is a difference only in the order of initializing the variables and checking for a continuation of the cycle. Moreover, it can be noted that only the array is taken into account in calculating the sum.Length, which was taken the very first. Obviously, this is:
int arrayLength2 = localRefToArray . Length ;in all four methods, there is an inline check for going beyond the array and it is performed for each element of the array.
mov edi , dword ptr [ edx + 4 ]
if ( i> = arrayLength2 ) throw new IndexOutOfRangeException ( ) ;
cmp ecx , edi
jae 04b12d39
It is possible to draw the first conclusion that it is a waste of time to start an additional variable in order to try to speed up the execution of a cycle. the compiler will do everything for us. Setting a variable with a cycle length makes sense only to increase the readability of the code.
A completely different situation with ForEach. Take three methods:
publicintForEachWithoutLength() {
int sum = 0;
foreach (int i in array) {
sum += i;
}
return sum;
}
publicintForEachWithLengthWithoutLocalVariable() {
int sum = 0;
foreach (int i in array) {
sum += i + array.Length;
}
return sum;
}
publicintForEachWithLengthWithLocalVariable() {
int sum = 0;
int length = array.Length;
foreach (int i in array) {
sum += i + length;
}
return sum;
}
And look at their code after JIT:
ForEachWithoutLength ()
;int sum = 0;
xor esi, esi
;int[] localRefToArray = this.array;
mov ecx, dword ptr [ecx+4]
;int i = 0;
xor edx, edx
;int arrayLength = localRefToArray.Length;
mov edi, dword ptr [ecx+4]
;if (arrayLength == 0) goto exit;
test edi, edi
jle exit
;int t = array[i];
loop:
mov eax, dword ptr [ecx+edx*4+8]
;sum+=i;
add esi, eax
;i++;
inc edx
;if (i < arrayLength) goto loop
cmp edi, edx
jg loop
;return sum;
exit:
mov eax, esi
xor esi, esi
;int[] localRefToArray = this.array;
mov ecx, dword ptr [ecx+4]
;int i = 0;
xor edx, edx
;int arrayLength = localRefToArray.Length;
mov edi, dword ptr [ecx+4]
;if (arrayLength == 0) goto exit;
test edi, edi
jle exit
;int t = array[i];
loop:
mov eax, dword ptr [ecx+edx*4+8]
;sum+=i;
add esi, eax
;i++;
inc edx
;if (i < arrayLength) goto loop
cmp edi, edx
jg loop
;return sum;
exit:
mov eax, esi
ForEachWithLengthWithoutLocalVariable ()
;int sum = 0;
xor esi, esi
;int[] localRefToArray = this.array;
mov ecx, dword ptr [ecx+4]
;int i = 0;
xor edx, edx
;int arrayLength = localRefToArray.Length;
mov edi, dword ptr [ecx+4]
;if (arrayLength == 0) goto exit
test edi, edi
jle exit
;int t = array[i];
loop:
mov eax, dword ptr [ecx+edx*4+8]
;sum+=i;
add esi, eax
;sum+=localRefToArray.Length;
add esi, dword ptr [ecx+4]
;i++;
inc edx
;if (i < arrayLength) goto loop
cmp edi, edx
jg loop
;return sum;
exit:
mov eax, esi
xor esi, esi
;int[] localRefToArray = this.array;
mov ecx, dword ptr [ecx+4]
;int i = 0;
xor edx, edx
;int arrayLength = localRefToArray.Length;
mov edi, dword ptr [ecx+4]
;if (arrayLength == 0) goto exit
test edi, edi
jle exit
;int t = array[i];
loop:
mov eax, dword ptr [ecx+edx*4+8]
;sum+=i;
add esi, eax
;sum+=localRefToArray.Length;
add esi, dword ptr [ecx+4]
;i++;
inc edx
;if (i < arrayLength) goto loop
cmp edi, edx
jg loop
;return sum;
exit:
mov eax, esi
ForEachWithLengthWithLocalVariable ()
;int sum = 0;
xor esi, esi
;int[] localRefToArray = this.array;
mov edx, dword ptr [ecx+4]
;int length = localRefToArray.Length;
mov ebx, dword ptr [edx+4]
;int i = 0;
xor ecx, ecx
;int arrayLength = localRefToArray.Length;
mov edi, dword ptr [edx+4]
;if (arrayLength == 0) goto exit;
test edi, edi
jle exit
;int t = array[i];
loop:
mov eax, dword ptr [edx+ecx*4+8]
;sum+=i;
add esi, eax
;sum+=length ;
add esi, ebx
;i++;
inc ecx
;if (i < arrayLength) goto loop
cmp edi, ecx
jg loop
;return sum;
exit:
mov eax, esi
xor esi, esi
;int[] localRefToArray = this.array;
mov edx, dword ptr [ecx+4]
;int length = localRefToArray.Length;
mov ebx, dword ptr [edx+4]
;int i = 0;
xor ecx, ecx
;int arrayLength = localRefToArray.Length;
mov edi, dword ptr [edx+4]
;if (arrayLength == 0) goto exit;
test edi, edi
jle exit
;int t = array[i];
loop:
mov eax, dword ptr [edx+ecx*4+8]
;sum+=i;
add esi, eax
;sum+=length ;
add esi, ebx
;i++;
inc ecx
;if (i < arrayLength) goto loop
cmp edi, ecx
jg loop
;return sum;
exit:
mov eax, esi
The first thing that catches your eye is that there are fewer assembler instructions compared to the for loop (for example, 12 instructions came out in the foreach for simple summation of elements, in for 15).
Screenshot comparison of For and ForEach

In fact, if you write a benchmark for vs foreach on 1,000,000 elements of an array, you get this picture for
sum+=array[i];
Method | Itemscount | Mean | Error | Stddev | Median | Ratio | RatioSD |
ForEach | 1,000,000 | 1.401 ms | 0.2691 ms | 0.7935 ms | 1.694 ms | 1.00 | 0.00 |
For | 1,000,000 | 1.586 ms | 0.3204 ms | 0.9447 ms | 1.740 ms | 1.23 | 0.65 |
sum+=array[i] + array.Length;
Method | Itemscount | Mean | Error | Stddev | Median | Ratio | RatioSD |
ForEach | 1,000,000 | 1.703 ms | 0.3010 ms | 0.8874 ms | 1.726 ms | 1.00 | 0.00 |
For | 1,000,000 | 1.715 ms | 0.2859 ms | 0.8430 ms | 1.956 ms | 1.13 | 0.56 |
Screenshot comparison of all three options foreach

Let's look at ForEachWithoutLength. In it, the length of the array is requested only once and there are no checks on the output of elements outside the array. This happens because the ForEach cycle firstly prohibits changing the collection inside the cycle, and secondly, it will definitely not go beyond the collection. Because of this, the JIT can afford to generally remove the check for output from the array.
Now let's take a close look at the ForEachWithLengthWithoutLocalVariable method. In his code, there is only one strange moment in that sum + = length occurs not with the local variable arrayLength stored previously, but with the new one that the program asks every time from memory. It turns out that the memory accesses for the array length will be N + 1, where N is the length of the array.
It remains to consider the method ForEachWithLengthWithLocalVariable. Its code is no different from ForEachWithLengthWithoutLocalVariable, except for working with a long array. The compiler again generated the local variable arrayLength, with which it checks that the array is not empty, but the compiler honestly preserved our explicit local variable length and addition in the body of the loop already happens to it. It turns out that this method only accesses memory twice to determine the length of the array. This difference in the number of memory accesses in real applications is very difficult to notice.
The assembler code of the methods examined was so simple because the methods themselves are simple. If there were more parameters in the method, there would already be work with the stack, the variables would not only be stored in registers and perhaps there would be more checks, but the basic logic would remain the same: introducing a local variable with an array length can make sense only to increase the code readability . In addition, it turned out that Foreach in an array is often faster than For.