为什么通过代表的不同方式的性能有如此大的差异?

时间:2012-08-21 21:12:37

标签: c# .net performance delegates

我试图比较三种不同的方式将委托传递给C#中的函数 - 由lambda,委托和直接引用。令我感到惊讶的是直接引用方法(即ComputeStringFunctionViaFunc(object[i].ToString))比其他方法慢六倍。有人知道这是为什么吗?

完整的代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.CompilerServices;

namespace FunctionInvocationTest
{
    class Program
    {
        static void Main(string[] args)
        {
            object[] objectArray = new object[10000000];
            for (int i = 0; i < objectArray.Length; ++i) { objectArray[i] = new object(); }

            ComputeStringFunction(objectArray[0]);
            ComputeStringFunctionViaFunc(objectArray[0].ToString);
            ComputeStringFunctionViaFunc(delegate() { return objectArray[0].ToString(); });
            ComputeStringFunctionViaFunc(() => objectArray[0].ToString());

            System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();
            s.Start();
            for (int i = 0; i < objectArray.Length; ++i)
            {
                ComputeStringFunction(objectArray[i]);
            }
            s.Stop();
            Console.WriteLine(s.Elapsed.TotalMilliseconds);

            s.Reset();
            s.Start();
            for (int i = 0; i < objectArray.Length; ++i)
            {
                ComputeStringFunctionViaFunc(delegate() { return objectArray[i].ToString(); });
            }
            s.Stop();
            Console.WriteLine(s.Elapsed.TotalMilliseconds);

            s.Reset();
            s.Start();
            for (int i = 0; i < objectArray.Length; ++i)
            {
                ComputeStringFunctionViaFunc(objectArray[i].ToString);
            }
            s.Stop();
            Console.WriteLine(s.Elapsed.TotalMilliseconds);

            s.Reset();
            s.Start();
            for (int i = 0; i < objectArray.Length; ++i)
            {
                ComputeStringFunctionViaFunc(() => objectArray[i].ToString());
            }
            s.Stop();
            Console.WriteLine(s.Elapsed.TotalMilliseconds);

            Console.ReadLine();
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        public static void ComputeStringFunction(object stringFunction)
        {
        }

        public static void ComputeStringFunctionViaFunc(Func<string> stringFunction)
        {
        }
    }
}

2 个答案:

答案 0 :(得分:6)

修复代码后,实际调用ToString() / stringFunction(),并使用Mono 2.10.9进行测量:

ComputeStringFunctionViaFunc(objectArray[i].ToString);很慢,因为object.ToString是虚拟的。检查每个对象是否覆盖ToString并且应该调用被覆盖的ToString。创建其他委托是为了引用非虚函数(快速),它直接调用虚函数(也很快)。当修改生成的IL以更改

时,可以看到这是原因
ldelem.ref
dup 
ldvirtftn instance string object::ToString()

ldelem.ref
ldftn instance string object::ToString()

总是指object.ToString,绝不是最重要的功能。这三种方法大约需要同时进行。

更新:另一种方法,直接绑定到objectArray[i]但仍然虚拟调用ToString

for (int i = 0; i < objectArray.Length; ++i)
{
    ComputeStringFunctionViaFunc(objectArray[i].ToStringHelper);
}

static class Extensions
{
    public static string ToStringHelper(this object obj)
    {
        return obj.ToString();
    }
}

也提供与其他非虚拟代表大致相同的时间。

答案 1 :(得分:4)

让我们检查你在每种情况下做了什么:

这家伙根本没有“创造”一个功能。它在数组中查找项目(在本例中为对象),并将该项目作为参数传递给函数:

// The cost of doing the array lookup happens right here, before 
// ComputeStringFunction is called
ComputeStringFunction(objectArray[i]);

这个创建一个无参数的委托并将其传递给一个函数。委托本身永远不会被称为:

// Because ComputeStringFunctionViaFunc doesn't do anything, the
// statement objectArray[i] is never evaluated, so the only cost 
// is that of creating a delegate
ComputeStringFunctionViaFunc(delegate() { return objectArray[i].ToString(); });

这个与第一个相同,除了不是在从数组中检索它之后立即传递项目,它会在其上调用.ToString()。同样,这里没有创建任何函数:

与第一个一样,这个先前有阵列查找的成本,但是然后创建一个引用项目的.ToString方法的委托(感谢@hvd捕获它)。与其他人一样,.ToString永远不会被评估。成本(再次,感谢@hvd)查找虚拟方法。

// The cost of doing the array lookup happens right here
ComputeStringFunctionViaFunc(objectArray[i].ToString);

最后,这个在数组项上使用lambda和闭包创建一个函数,并将该lambda传递给函数。根据函数签名,lambda可以编译或不编译:

// Again, we create a delegate but don't call it, so the array
// lookup and .ToString are never evaluated.
ComputeStringFunctionViaFunc(() => objectArray[i].ToString());

这里需要注意的重要一点是,数组查找的评估在第二个和第四个中延迟,而在第一个和第三个中没有延迟。

这些测试有些无意义,因为它们都做了完全不同的事情。几乎可以肯定更好的方式来创建委托。