使用具体类型而不是界面是否更好的性能?

时间:2010-11-23 14:24:42

标签: c# .net performance

我已经遇到一些规则(建议)使用具体的ListDictionary而不是IListIDictionary,因为示例测试显示通过界面访问慢一点例如,向列表中添加10000个值,然后在列表上执行Count 10亿次,这表明通过接口执行此操作比通过具体类执行它慢28倍。即,通过具体类需要80ms,通过界面需要2800ms,这表明它通过界面的速度非常慢。鉴于此,使用具体类是合理的。有没有理由说接口这么慢? (可能更多的是针对那些了解更多关于.net内部的人。)

7 个答案:

答案 0 :(得分:8)

我认为如果你看一下反汇编就很明显了:

IList版本编译为:

            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 
            }

IList.Count的访问权限被编译为call指令。

另一方面List版本内联:

            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 
            }

这里没有call指令。只是循环中的mov,inc,cmp和jl指令。当然这更快。

但请记住:通常情况下,你用你的列表内容事情,你不只是迭代它。这通常比单个函数调用花费的时间长,因此调用接口方法很少会导致任何性能问题。

答案 1 :(得分:6)

使用接口的主要原因是灵活性,关注点分离等。

所以我仍然建议在大多数情况下使用接口(不是全部,只是在适当的地方)并且只考虑在存在实际性能问题时切换到具体类。

并且,在没有GC.Collect()的情况下运行基准测试并且在发布模式下,我得到96和560毫秒。相差很小。

答案 2 :(得分:6)

您的表现测试显然是错误的。你正在测试很多不同的东西。首先,GC.Collect会触发终结器线程,这会影响其后运行的所有内容的性能。接下来,您不仅要测试调用接口方法所花费的时间,还要花费大量时间将数据复制到新数组(因为您正在创建大型数组)并收集它们! - 因此接口调用之间存在差异实例调用将完全迷失。

这是一个测试,我测试了接口调用的原始性能开销。在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();
        }
    }
}

输出:

Interface: 4861
Concrete: 4236

答案 3 :(得分:3)

这在您的应用程序中确实是一个问题,接口可以使您的代码更好,更容易重用/维护

如果你真的需要提高性能,首先尝试改进算法,例如,你真的需要计算10亿次元素吗?你不能把计数存储在某个地方,并有一些标志,表明元素已经改变,你需要重新计算它吗?

据说Performance impact of changing to generic interfaces的问题解决了接口的性能

答案 4 :(得分:3)

未经优化的DEBUG模式没有明显的性能差异,而且使用.Net 3.5,Visual Stusio 2008,RELEASE模式的性能提升了35-40%。

Debug:
 List test,  ms: 1234.375
 IList test, ms: 1218.75
Release:
 List test,  ms: 609.375
 IList test, ms: 968.75

测试代码:

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

答案 5 :(得分:2)

这是我用来查看差异的测试代码:

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


      }
}

请注意,垃圾收集似乎不会影响此测试。

答案 6 :(得分:0)

here详细说明了IDictionaryDictionary慢的原因。

如果枚举IDictionary,则确实会产生垃圾,垃圾有时会由GC收集。那就是您看到性能差异的地方。如果您问我,私有成员应始终使用其具体类型进行声明,从而允许编译器至少考虑类中特定于类型的优化(例如枚举)。