I was doing some performance metrics and I ran into something that seems quite odd to me. I time the following two functions:
private static void DoOne()
{
List<int> A = new List<int>();
for (int i = 0; i < 200; i++) A.Add(i);
int s=0;
for (int j = 0; j < 100000; j++)
{
for (int c = 0; c < A.Count; c++) s += A[c];
}
}
private static void DoTwo()
{
List<int> A = new List<int>();
for (int i = 0; i < 200; i++) A.Add(i);
IList<int> L = A;
int s = 0;
for (int j = 0; j < 100000; j++)
{
for (int c = 0; c < L.Count; c++) s += L[c];
}
}
Even when compiling in release mode, the timings results were consistently showing that DoTwo takes ~100 longer then DoOne:
DoOne took 0.06171706 seconds.
DoTwo took 8.841709 seconds.
Given the fact the List directly implements IList I was very surprised by the results. Can anyone clarify this behavior?
Responding to questions, here is the full code and an image of the project build preferences:
Dead Image Link
using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Collections;
namespace TimingTests
{
class Program
{
static void Main(string[] args)
{
Stopwatch SW = new Stopwatch();
SW.Start();
DoOne();
SW.Stop();
Console.WriteLine(" DoOne took {0} seconds.", ((float)SW.ElapsedTicks) / Stopwatch.Frequency);
SW.Reset();
SW.Start();
DoTwo();
SW.Stop();
Console.WriteLine(" DoTwo took {0} seconds.", ((float)SW.ElapsedTicks) / Stopwatch.Frequency);
}
private static void DoOne()
{
List<int> A = new List<int>();
for (int i = 0; i < 200; i++) A.Add(i);
int s=0;
for (int j = 0; j < 100000; j++)
{
for (int c = 0; c < A.Count; c++) s += A[c];
}
}
private static void DoTwo()
{
List<int> A = new List<int>();
for (int i = 0; i < 200; i++) A.Add(i);
IList<int> L = A;
int s = 0;
for (int j = 0; j < 100000; j++)
{
for (int c = 0; c < L.Count; c++) s += L[c];
}
}
}
}
Thanks for all the good answers (especially @kentaromiura). I would have closed the question, though I feel we still miss an important part of the puzzle. Why would accessing a class via an interface it implements be so much slower? The only difference I can see is that accessing a function via an Interface implies using virtual tables while the normally the functions can be called directly. To see whether this is the case I made a couple of changes to the above code. First I introduced two almost identical classes:
public class VC
{
virtual public int f() { return 2; }
virtual public int Count { get { return 200; } }
}
public class C
{
public int f() { return 2; }
public int Count { get { return 200; } }
}
As you can see VC is using virtual functions and C doesn't. Now to DoOne and DoTwo:
private static void DoOne()
{ C a = new C();
int s=0;
for (int j = 0; j < 100000; j++)
{
for (int c = 0; c < a.Count; c++) s += a.f();
}
}
private static void DoTwo()
{
VC a = new VC();
int s = 0;
for (int j = 0; j < 100000; j++)
{
for (int c = 0; c < a.Count; c++) s += a.f();
}
}
And indeed:
DoOne took 0.01287789 seconds.
DoTwo took 8.982396 seconds.
This is even more scary - virtual function calls 800 times slower?? so a couple of question to the community:
Boaz
The main difference between List and IList in C# is that List is a class that represents a list of objects which can be accessed by index while IList is an interface that represents a collection of objects which can be accessed by index.
IEnumerable is conceptually faster than List because of the deferred execution. Deferred execution makes IEnumerable faster because it only gets the data when needed. Contrary to Lists having the data in-memory all the time.
A note to everyone out there who is trying to benchmark stuff like this.
Do not forget that the code is not jitted until the first time it runs. That means that the first time you run a method, the cost of running that method could be dominated by the time spent loading the IL, analyzing the IL, and jitting it into machine code, particularly if it is a trivial method.
If what you're trying to do is compare the "marginal" runtime cost of two methods, it's a good idea to run both of them twice and consider only the second runs for comparison purposes.
Profiling one on one:
Testing with Snippet compiler.
using your code results:
0.043s vs 0.116s
eliminating Temporary L
0.043s vs 0.116s - ininfluent
by caching A.count in cmax on both Methods
0.041s vs 0.076s
IList<int> A = new List<int>();
for (int i = 0; i < 200; i++) A.Add(i);
int s = 0;
for (int j = 0; j < 100000; j++)
{
for (int c = 0,cmax=A.Count;c< cmax; c++) s += A[c];
}
Now I will try to slow down DoOne, first try, casting to IList before add:
for (int i = 0; i < 200; i++) ((IList<int>)A).Add(i);
0,041s 0,076s - so add is ininfluent
so it remains only one place where the slowdown can happen : s += A[c];
so I try this:
s += ((IList<int>)A)[c];
0.075s 0.075s - TADaaan!
so seems that accessing Count or a index element is slower on the interfaced version:
EDIT: Just for fun take a look at this:
for (int c = 0,cmax=A.Count;c< cmax; c++) s += ((List<int>)A)[c];
0.041s 0.050s
so is not a cast problem, but a reflection one!
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