Reputation: 1817
In the following scenario, is example 1 faster than example 2? Why?
Example 1
int c = myArray.Count;
for (int i = 0; i < c; i++)
{
Console.WriteLine(myArray[i]);
}
Example 2
for (int i = 0; i < myArray.Count; i++)
{
Console.WriteLine(myArray[i]);
}
Upvotes: 3
Views: 130
Reputation: 13676
You can test it by yourself. For that you'd need a workbench:
class WorkBench
{
private static readonly Stopwatch S = new Stopwatch();
private static long[] RunOnce()
{
var results = new long[3];
var myArray = Enumerable.Range(0, 1000000).ToList();
int x = 1;
S.Restart();
for (int i = 0; i < myArray.Count; i++)
{
x = i + 1;
}
S.Stop();
results[0] = S.ElapsedTicks;
S.Restart();
int c = myArray.Count;
for (int i = 0; i < c; i++)
{
x = i - 1;
}
S.Stop();
results[1] = S.ElapsedTicks;
results[2] = x;
return results;
}
private static void Main(string[] args)
{
var results = new List<Tuple<long, long>>();
for (int i = 0; i < 1500; i++)
{
var workBenchResult = RunOnce();
results.Add(Tuple.Create(workBenchResult[0], workBenchResult[1]));
}
var average = Tuple.Create(results.Average(r => r.Item1), results.Average(r => r.Item2));
Console.WriteLine($"Average 1: {Math.Round(average.Item1, 4)}");
Console.WriteLine($"Average 2: {Math.Round(average.Item2, 4)}");
}
On my machine results are:
DEBUG : 7852 and 6631 (with variable faster)
RELEASE : 1117 and 1127 (almost the same stuff)
General idea is this:
In Debug mode call to array's (collection) property is not optimized and therefore it adds overhead.
In Release mode this call is optimized and because declaring a new int variable explicitly will allocate space in stack etc. It takes a bit more time than optimized code that probably creates a shortcut by saving a pointer to Count
property and accessing this property directly.
Upvotes: 2
Reputation: 3775
Lets take the IL code and see what is going on in Release configuration.
/* 0x0000027B 6F0D00000A */ IL_001F: callvirt instance int32 class [System.Collections]System.Collections.Generic.List`1::get_Count() /* 0x00000280 0B */ IL_0024: stloc.1 /* 0x00000281 16 */ IL_0025: ldc.i4.0 /* 0x00000282 0C */ IL_0026: stloc.2 /* 0x00000283 2B10 */ IL_0027: br.s IL_0039 // loop start (head: IL_0039) /* 0x00000285 06 */ IL_0029: ldloc.0 /* 0x00000286 08 */ IL_002A: ldloc.2 /* 0x00000287 6F0E00000A */ IL_002B: callvirt instance !0 class [System.Collections]System.Collections.Generic.List`1::get_Item(int32) /* 0x0000028C 280F00000A */ IL_0030: call void [System.Console]System.Console::WriteLine(char) /* 0x00000291 08 */ IL_0035: ldloc.2 /* 0x00000292 17 */ IL_0036: ldc.i4.1 /* 0x00000293 58 */ IL_0037: add /* 0x00000294 0C */ IL_0038: stloc.2 /* 0x00000295 08 */ IL_0039: ldloc.2 /* 0x00000296 07 */ IL_003A: ldloc.1 /* 0x00000297 32EC */ IL_003B: blt.s IL_0029 // end loop /* 0x00000299 16 */ IL_003D: ldc.i4.0 /* 0x0000029A 0D */ IL_003E: stloc.3 /* 0x0000029B 2B10 */ IL_003F: br.s IL_0051 // loop start (head: IL_0051) /* 0x0000029D 06 */ IL_0041: ldloc.0 /* 0x0000029E 09 */ IL_0042: ldloc.3 /* 0x0000029F 6F0E00000A */ IL_0043: callvirt instance !0 class [System.Collections]System.Collections.Generic.List`1::get_Item(int32) /* 0x000002A4 280F00000A */ IL_0048: call void [System.Console]System.Console::WriteLine(char) /* 0x000002A9 09 */ IL_004D: ldloc.3 /* 0x000002AA 17 */ IL_004E: ldc.i4.1 /* 0x000002AB 58 */ IL_004F: add /* 0x000002AC 0D */ IL_0050: stloc.3 /* 0x000002AD 09 */ IL_0051: ldloc.3 /* 0x000002AE 06 */ IL_0052: ldloc.0 /* 0x000002AF 6F0D00000A */ IL_0053: callvirt instance int32 class [System.Collections]System.Collections.Generic.List`1::get_Count() /* 0x000002B4 32E7 */ IL_0058: blt.s IL_0041 // end loop
There is one obvious difference between both, in the latter method you are calling virtual instance method on each iteration and on the other only once before the loop.
IL instructions are relatively the same number so unless you think that this callvirt
(why callvirt for instance method? because it has nice null check that comes with it and compiler uses it for non-virtual methods as well) instruction will drag you down I am suggesting you do choose what is best practice that potentially small performance tweak will not worth it I guarantee it, not to mention that JIT can also do some optimizations - I will be not surprised.
UPDATE: Benchmarks with BenchmarkDotNet with attaached debugger.
Method | Mean | Error | StdDev |
------------- |---------:|----------:|----------:|
OutsideCount | 25.04 ns | 0.3334 ns | 0.2955 ns |
InsideCount | 26.13 ns | 0.5295 ns | 0.6502 ns |
Foreach | 40.59 ns | 0.3848 ns | 0.3599 ns |
Again this is very hardware specific, but showing it for the sake of argument.
Upvotes: 3
Reputation: 6157
Arrays don't have a property named Count
only a method Count()
. Using Example 2 that method will be run each iteration, which takes significantly more time than having a variable predefined.
Assuming you use the property Count
from an ICollection
or Length
from an array
there's practically no significant difference between the examples performance-wise and you're best off taking the most readable solution.
Upvotes: 0