10

我试图比较在 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)
        {
        }
    }
}
4

2 回答 2

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

也给出了与其他非虚拟代表大致相同的时间安排。

于 2012-08-21T21:38:36.147 回答
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());

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

这些测试有点荒谬,因为它们都做完全不同的事情。几乎可以肯定有更好的方法来定时委托创建。

于 2012-08-21T21:42:32.780 回答