c#Generics - 意外的性能结果

时间:2013-12-12 21:53:00

标签: c# performance generics

我相信微软声称在处理引用类型时,泛型比使用普通多态更快。但是,以下简单测试(64位VS2012)将指示其他情况。我通常使用多态性使秒表时间快10%。我误解了结果吗?

public interface Base { Int64 Size { get; } }
public class Derived : Base { public Int64 Size { get { return 10; } } }

public class GenericProcessor<TT> where TT : Base
{
    private Int64 sum;
    public GenericProcessor(){ sum = 0; }
    public void process(TT o){ sum += o.Size; }
    public Int64 Sum { get { return sum; } }
}
public class PolymorphicProcessor
{
    private Int64 sum;
    public PolymorphicProcessor(){ sum = 0; }
    public void process(Base o){ sum += o.Size; }
    public Int64 Sum { get { return sum; } }
}
static void Main(string[] args)
{
    var generic_processor = new GenericProcessor<Derived>();
    var polymorphic_processor = new PolymorphicProcessor();
    Stopwatch sw = new Stopwatch();
    int N = 100000000;
    var derived = new Derived();

    sw.Start();
    for (int i = 0; i < N; ++i) generic_processor.process(derived);
    sw.Stop();
    Console.WriteLine("Sum ="+generic_processor.Sum + " Generic performance = " + sw.ElapsedMilliseconds + " millisec");

    sw.Restart();
    sw.Start();
    for (int i = 0; i < N; ++i) polymorphic_processor.process(derived);
    sw.Stop();
    Console.WriteLine("Sum ="+polymorphic_processor.Sum+ " Poly performance = " + sw.ElapsedMilliseconds + " millisec");

更令人惊讶(并且令人困惑)的是,如果我按如下方式向处理器的多态版本添加类型转换,则它的运行速度始终比通用版本快20%。

        public void process(Base trade)
        {
            sum += ((Derived)trade).Size; // cast not needed - just an experiment
        }

这里发生了什么?我理解泛型在处理原始类型时可以帮助避免昂贵的装箱和拆箱,但我在这里严格处理引用类型。

2 个答案:

答案 0 :(得分:2)

使用Ctrl-F5(无调试器)在.NET 4.5 x64下执行测试。 N也增加了10倍。这样,无论测试的顺序如何,结果都可以可靠地重现。


对于ref类型的泛型,你仍然可以获得相同的vtable /接口查找,因为对于所有ref类型只有一个编译方法。 Derived没有专业化。基于此,执行callvirt的执行情况应该相同。

此外,泛型方法有一个隐藏方法参数typeof(T)(因为这允许您在通用代码中实际编写typeof(T)!)。这是额外的开销,解释了为什么通用版本更慢。

为什么演员表比接口电话更快?演员表只是一个指针比较和一个完美可预测的分支。在演员之后,对象的具体类型是已知的,允许更快的呼叫。

if (trade.GetType() != typeof(Derived)) throw;
Derived.Size(trade); //calling directly the concrete method, potentially inlining it

所有这些都是受过教育的猜测。通过查看反汇编来验证。

如果添加演员表,您将获得以下程序集:

enter image description here

我的装配技巧不足以完全解码。但是:

  1. 16加载Derived的vtable ptr

  2. 22和#25是测试vtable的分支。这样就完成了演员。

  3. 在#32处演员完成。请注意,在此之后没有电话。 Size已被列入内容。
  4. 35 lea实现了添加

  5. 39存储回this.sum

  6. 同样的技巧适用于通用版本(((Derived)(Base)o).Size)。

答案 1 :(得分:1)

我相信Servy是正确的,这是你的测试的问题。我颠倒了测试的顺序(只是预感):

internal class Program
{
    public interface Base
    {
        Int64 Size { get; }
    }

    public class Derived : Base
    {
        public Int64 Size
        {
            get
            {
                return 10;
            }
        }
    }

    public class GenericProcessor<TT>
        where TT : Base
    {
        private Int64 sum;

        public GenericProcessor()
        {
            sum = 0;
        }

        public void process(TT o)
        {
            sum += o.Size;
        }

        public Int64 Sum
        {
            get
            {
                return sum;
            }
        }
    }

    public class PolymorphicProcessor
    {
        private Int64 sum;

        public PolymorphicProcessor()
        {
            sum = 0;
        }

        public void process(Base o)
        {
            sum += o.Size;
        }

        public Int64 Sum
        {
            get
            {
                return sum;
            }
        }
    }

    private static void Main(string[] args)
    {
        var generic_processor = new GenericProcessor<Derived>();
        var polymorphic_processor = new PolymorphicProcessor();
        Stopwatch sw = new Stopwatch();
        int N = 100000000;
        var derived = new Derived();
        sw.Start();
        for (int i = 0; i < N; ++i) polymorphic_processor.process(derived);
        sw.Stop();
        Console.WriteLine(
            "Sum =" + polymorphic_processor.Sum + " Poly performance = " + sw.ElapsedMilliseconds + " millisec");


        sw.Restart();
        sw.Start();
        for (int i = 0; i < N; ++i) generic_processor.process(derived);
        sw.Stop();
        Console.WriteLine(
            "Sum =" + generic_processor.Sum + " Generic performance = " + sw.ElapsedMilliseconds + " millisec");

        Console.Read();
    }
    }

在这种情况下,我的测试中的多态性较慢。这表明第一次测试明显慢于第二次测试。它可能是第一次加载类,抢占,谁知道......

我只想指出,我并不认为仿制药更快或更快。我只是想证明这些类型的测试不会以某种方式形成一种情况。