I have run into some rules (recommendations) to use concrete List
and Dictionary
rather than IList
and IDictionary
, given the sample tests that show accessing through the interface is quite a bit slower. For example, adding 10000 values to a list and then doing a Count
on the list 1 billion times shows that doing it through an interface is 28 times slower then doing it through the concrete class. Ie, through the concrete class it takes 80ms, through the interface it takes 2800ms which shows how really slow it is through the interface. Given this would it be reasonable use the concrete class. Is there a reason why the interface is so much slower? (Probably more directed at someone who know more about the internals of .net).
I think it's quite obvious if you look at the disassembly:
The IList
version is compiled to:
for (int i = 0; i < 1000000000; i++)
0000003d xor edi,edi
{
count = lst.Count;
0000003f mov ecx,esi
00000041 call dword ptr ds:[00280024h]
00000047 mov ebx,eax
for (int i = 0; i < 1000000000; i++)
00000049 inc edi
0000004a cmp edi,3B9ACA00h
00000050 jl 0000003F
}
The access to IList.Count
is compiled into a call
instruction.
The List
version on the other hand is inlined:
for (int i = 0; i < 1000000000; i++)
0000003a xor edx,edx
0000003c mov eax,dword ptr [esi+0Ch]
0000003f mov esi,eax
00000041 inc edx
00000042 cmp edx,3B9ACA00h
00000048 jl 0000003F
}
No call
instruction here. Just a mov, inc, cmp and jl instruction in the loop. Of course this is faster.
But remember: Usually, you're doing something with the contents of your list, you're not just iterating over it. This will usually take much longer than a single function call, so calling interface methods will rarely cause any performance problems.
The main reason for using the interfaces is flexibility, separation of concerns etc.
So I would still advice to use interfaces in most case (not all, just where appropriate) and only consider switching to the concrete classes when there is a real performance issue.
And, after running your benchmark without the GC.Collect()
and in Release mode I get 96 and 560ms. A lot smaller difference.
Your performance tests are clearly wrong. You are testing a lot of different things. First of all GC.Collect
does trigger the finalizer thread, which effects performance of everything that runs after it. Next, you not only test the amount of time it takes to call interface methods, but a lot of time goes into copying data to new arrays (since you're creating large arrays) -and collecting them!- so the difference between interface calls and instance calls will totally get lost.
Here is an test were I test the raw performance overhead of interface calls. When run in release mode outside of Visual Studio:
public interface IMyInterface
{
void InterfaceMethod();
}
public class MyClass : IMyInterface
{
[MethodImpl(MethodImplOptions.NoInlining)]
public void InterfaceMethod()
{
}
[MethodImpl(MethodImplOptions.NoInlining)]
public void InstanceMethod()
{
}
}
class Program
{
static void Main(string[] args)
{
// JITting everyting:
MyClass c = new MyClass();
c.InstanceMethod();
c.InterfaceMethod();
TestInterface(c, 1);
TestConcrete(c, 1);
Stopwatch watch = Stopwatch.StartNew();
watch.Start();
var x = watch.ElapsedMilliseconds;
// Starting tests:
watch = Stopwatch.StartNew();
TestInterface(c, Int32.MaxValue - 2);
var ms = watch.ElapsedMilliseconds;
Console.WriteLine("Interface: " + ms);
watch = Stopwatch.StartNew();
TestConcrete(c, Int32.MaxValue - 2);
ms = watch.ElapsedMilliseconds;
Console.WriteLine("Concrete: " + ms);
}
static void TestInterface(IMyInterface iface, int iterations)
{
for (int i = 0; i < iterations; i++)
{
iface.InterfaceMethod();
}
}
static void TestConcrete(MyClass c, int iterations)
{
for (int i = 0; i < iterations; i++)
{
c.InstanceMethod();
}
}
}
Output:
Interface: 4861
Concrete: 4236
// Starting tests
the code calls TestInterface
twice and doesn't call TestConcrete
. –
Alonaalone Is this really a problem in your application, interfaces can make you're code better and easier to reuse/maintain
If you really need to improve the performance try at first to improve the algorithm, for instance, do you really need to count the elements 1 billion time? can't you store the count somewhere and have some sort of flag indicating that the elements have changed and you need to recount it?
That being said the question at Performance impact of changing to generic interfaces adresses the performance of interfaces
There was no apparent performance difference in unoptimized DEBUG mode and about 35-40% improvement in RELEASE mode with .Net 3.5, Visual Stusio 2008.
Debug:
List test, ms: 1234.375
IList test, ms: 1218.75
Release:
List test, ms: 609.375
IList test, ms: 968.75
Test code:
List<int> list = new List<int>();
var start = DateTime.Now;
for (int i = 0; i < 50000000; i++) list.Add(i);
for (int i = 0; i < 50000000; i++) list[i] = 0;
var span = DateTime.Now - start;
Console.WriteLine("List test, ms: {0}", span.TotalMilliseconds);
IList<int> ilist = new List<int>();
start = DateTime.Now;
for (int i = 0; i < 50000000; i++) ilist.Add(i);
for (int i = 0; i < 50000000; i++) ilist[i] = 0;
span = DateTime.Now - start;
Console.WriteLine("IList test, ms: {0}", span.TotalMilliseconds);
This is the test code I was using to see the differences:
using System;
using System.Collections.Generic;
using System.Diagnostics;
public class test1
{
static void Main(string[] args)
{
Stopwatch sw = new Stopwatch();
const int NUM_ITEMS = 10000;
const int NUM_LOOPS2 = 1000000000;
List<int> lst = new List<int>(NUM_ITEMS);
IList<int> ilst = lst;
for (int i = 0; i < NUM_ITEMS; i++)
{
lst.Add(i);
}
int count = 0;
sw.Reset();
//GC.Collect();
sw.Start();
for (int i = 0; i < NUM_LOOPS2; i++)
{
count = lst.Count;
}
sw.Stop();
Console.Out.WriteLine("Took " + (sw.ElapsedMilliseconds) + "ms - 1.");
sw.Reset();
//GC.Collect();
sw.Start();
for (int i = 0; i < NUM_LOOPS2; i++)
{
count = ilst.Count;
}
sw.Stop();
Console.Out.WriteLine("Took " + (sw.ElapsedMilliseconds) + "ms - 2.");
}
}
Note that the garbage collection doesn't seem to affect this test.
The reason for IDictionary
being slower than Dictionary
is explained in detail here.
If you enumerate an IDictionary
you will indeed produce garbage which will at some point be collected by the GC. That's where you see the difference in performance. If you ask me, private members should always be declared with their concrete type, allowing the compiler to consider type-specific optimizations (like enumeration) within the class, at least.
© 2022 - 2024 — McMap. All rights reserved.
GC.Collect()
DOES trigger background activity: the finalizer thread. – DalorisGC.Collect()
call), and I was seeing an increase in timing more on the order of ~8x. – Dian