is it better performance wise to use the concrete type rather than the interface
Asked Answered
M

7

19

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).

Misha answered 23/11, 2010 at 14:24 Comment(13)
What language has such miserable performance with interfaces?Harriette
IList<int> ilst = lst; for (int i = 0; i < 10000; i++) { lst.Add(i); } int count = 0; GC.Collect(); for (int i = 0; i < 1000000000; i++) { count = lst.Count; }Misha
A performance difference of a factor 100 will seldom be seen. I doubt the correctness of your performance measurements. Can you show us your code?Daloris
Why are you forcing a GC collect in the middle of that code? There isn't anything to collect and it's not guaranteed to run at that point anyway potentially screwing your benchmark.Barrows
@user455095: That GC.Collect() may trigger some background activity.Shack
@Henk Holterman: GC.Collect() DOES trigger background activity: the finalizer thread.Daloris
I just tested a modified version of his code (including the removal of the GC.Collect() call), and I was seeing an increase in timing more on the order of ~8x.Dian
@Spartan: Me too. 8x is still considerable but it's not 28Shack
@Steven: only if there are finalizers to run. Not likely here.Shack
this is the code I was using, note that if I took this code and put it into an nunit test, its considerably slower for both the interface and direct access and the times are almost the same in an nunit test. I guess its probably not a good idea to do preforance testing in an nunit test anyhow.Misha
@dicroce: C++, C# and many others. Few languages can inline virtual method calls.Freddyfredek
The problem here is not that an indirect interface method call is so slow, it is that the Count property is so very, very fast. You can make anything that's fast slow by doing it a billion times.Triplett
in most cases interfaces are not needed, think about this who uses your code, no one, so don't waste time trying to implement the best interfaces, interfaces just add more complexity to the project and you end up with more functional errors than there should beDebidebilitate
F
11

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.

Freddyfredek answered 23/11, 2010 at 15:0 Comment(4)
thanks, this helps to explain the perforance differences anyhow and it is true what you in the loop would take more time but I guess its more like trying to optimize every line of code in the loop to make it go as fast as possible if perforance is very important.Misha
@Steven: Actually, it's assembly, not IL. But thanks anyway ;-)Freddyfredek
That's what I of course meant. ;-) IL doesn't really explain anything. We need to look at assembly.Daloris
in most cases interfaces are not needed, think about this who uses your code, no one, so don't waste time trying to implement the best interfaces, interfaces just add more complexity to the project and you end up with more functional errors than there should beDebidebilitate
S
8

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.

Shack answered 23/11, 2010 at 14:31 Comment(0)
D
6

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
Daloris answered 23/11, 2010 at 14:48 Comment(4)
This just had a correction made by user:1198075 that was rejected - after the line // Starting tests the code calls TestInterface twice and doesn't call TestConcrete.Alonaalone
Why was the edit rejected? This answer contains an error and is misleading. The result if TestInterface is compared to TestConcrete (and not to itself) is: Interface: 5791 Concrete: 4470Asinine
@JohanNilsson and qujck: There was indeed a bug in the test. I corrected the test and the output.Daloris
@JohanNilsson IMO the majority of edits made to answers seem to get rejected - I cannot explain why. I noticed this edit was correct so I added a comment for Steven to make the change himself ...Alonaalone
R
3

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

Robey answered 23/11, 2010 at 14:43 Comment(2)
we could but I guess its a case of that its possible that any code could end up being executed a billion times(ok that sounds a little crazy but its possible) and to proect against that you could just say to use the concrete class. I guess in this case it would only be for Lists and dictionaries rather than every possible interface .Misha
My point is that usually algorithm improvements can have a huge effect in the performance (the removal of the GC.Collect in your code is a good example) making microptimizations unecessary but yes, using concrete classes seems to be faster then using interfacesRobey
C
3

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);
Corwun answered 23/11, 2010 at 15:41 Comment(0)
M
2

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.

Misha answered 23/11, 2010 at 14:49 Comment(0)
E
1

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.

Elviselvish answered 1/9, 2020 at 13:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.