I was doing other experiments until this strange behaviour caught my eye.
code is compiled in x64 release.
if key in 1, the 3rd run of List method cost 40% more time than the first 2. output is
List costs 9312
List costs 9289
Array costs 12730
List costs 11950
if key in 2, the 3rd run of Array method cost 30% more time than the first 2. output is
Array costs 8082
Array costs 8086
List costs 11937
Array costs 12698
You can see the pattern, the complete code is attached following (just compile and run): {the code presented is minimal to run the test. The actually code used to get reliable result is more complicated, I wrapped the method and tested it 100+ times after proper warmed up}
class ListArrayLoop
{
readonly int[] myArray;
readonly List<int> myList;
readonly int totalSessions;
public ListArrayLoop(int loopRange, int totalSessions)
{
myArray = new int[loopRange];
for (int i = 0; i < myArray.Length; i++)
{
myArray[i] = i;
}
myList = myArray.ToList();
this.totalSessions = totalSessions;
}
public void ArraySum()
{
var pool = myArray;
long sum = 0;
for (int j = 0; j < totalSessions; j++)
{
sum += pool.Sum();
}
}
public void ListSum()
{
var pool = myList;
long sum = 0;
for (int j = 0; j < totalSessions; j++)
{
sum += pool.Sum();
}
}
}
class Program
{
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
ListArrayLoop test = new ListArrayLoop(10000, 100000);
string input = Console.ReadLine();
if (input == "1")
{
sw.Start();
test.ListSum();
sw.Stop();
Console.WriteLine("List costs {0}",sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
test.ListSum();
sw.Stop();
Console.WriteLine("List costs {0}", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
test.ArraySum();
sw.Stop();
Console.WriteLine("Array costs {0}", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
test.ListSum();
sw.Stop();
Console.WriteLine("List costs {0}", sw.ElapsedMilliseconds);
}
else
{
sw.Start();
test.ArraySum();
sw.Stop();
Console.WriteLine("Array costs {0}", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
test.ArraySum();
sw.Stop();
Console.WriteLine("Array costs {0}", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
test.ListSum();
sw.Stop();
Console.WriteLine("List costs {0}", sw.ElapsedMilliseconds);
sw.Reset();
sw.Start();
test.ArraySum();
sw.Stop();
Console.WriteLine("Array costs {0}", sw.ElapsedMilliseconds);
}
Console.ReadKey();
}
}
Contrived problems give you contrived answers.
Optimization should be done after code is written and not before. Write your solution the way that is easiest to understand and maintain. Then if the program is not fast enough for your use case, then you use a profiling tool and go back and see where the actual bottleneck is, not where you "think" it is.
Most optimizations people try to do in your situation is spending 6 hours to do something that will decrease the run time by 1 second. Most small programs will not be run enough times to offset the cost you spent trying to "optimize" it.
That being said this is a strange edge case. I modified it a bit and am running it though a profiler but I need to downgrade my VS2010 install so I can get .NET framework source stepping back.
I ran though the profiler using the larger example, I can find no good reason why it would take longer.
Your issue is your test. When you are benchmarking code there are a couple of guiding principals you should always follow:
So using these guidelines and rewriting your tests I get the following results:
Run 1
Enter test number (1|2): 1 ListSum averages 776 ListSum averages 753 ArraySum averages 1102 ListSum averages 753 Press any key to continue . . .
Run 2
Enter test number (1|2): 2 ArraySum averages 1155 ArraySum averages 1102 ListSum averages 753 ArraySum averages 1067 Press any key to continue . . .
So here is the final test code used:
static void Main(string[] args)
{
//We just need a single-thread for this test.
Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(2);
System.Threading.Thread.BeginThreadAffinity();
Console.Write("Enter test number (1|2): ");
string input = Console.ReadLine();
//perform the action just a few times to jit the code.
ListArrayLoop warmup = new ListArrayLoop(10, 10);
Console.WriteLine("Performing warmup...");
Test(warmup.ListSum);
Test(warmup.ArraySum);
Console.WriteLine("Warmup complete...");
Console.WriteLine();
ListArrayLoop test = new ListArrayLoop(10000, 10000);
if (input == "1")
{
Test(test.ListSum);
Test(test.ListSum);
Test(test.ArraySum);
Test(test.ListSum);
}
else
{
Test(test.ArraySum);
Test(test.ArraySum);
Test(test.ListSum);
Test(test.ArraySum);
}
}
private static void Test(Action test)
{
long totalElapsed = 0;
for (int counter = 10; counter > 0; counter--)
{
try
{
var sw = Stopwatch.StartNew();
test();
totalElapsed += sw.ElapsedMilliseconds;
}
finally { }
GC.Collect(0, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
//cooldown
for (int i = 0; i < 100; i++)
System.Threading.Thread.Sleep(0);
}
Console.WriteLine("{0} averages {1}", test.Method.Name, totalElapsed / 10);
}
Note: Some people may debate about the usefulness of the cool-down; However, everyone agrees that even if it's not helpful, it is not harmful. I find that on some tests it can yield a more reliable result; however, in the example above I doubt it makes any difference.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With