根据这个问题- Pass Method as Parameter using C#和我的一些个人经验,我想更多地了解在C#中调用委托与只调用方法的性能。
虽然委托非常方便,但我有一个通过委托进行大量回调的应用程序,当我们重写此应用程序以使用回调接口时,速度得到了数量级的提高。这是在.NET 2.0中,所以我不确定在3和4中事情发生了什么变化。
如何在编译器/CLR内部处理对委托的调用?这对方法调用的性能有何影响?
编辑-澄清我所说的委托和回调接口是什么意思。
对于异步调用,我的类可以提供调用者可以订阅的OnComplete事件和相关的委托。
或者,我可以使用调用者实现的OnComplete方法创建一个ICallback接口,然后将自己注册到类中,该类将在完成时调用该方法(即Java处理这些事情的方式)。
发布于 2010-01-18 06:19:59
我还没有看到这种效果--我肯定从来没有遇到过它是一个瓶颈。
这里有一个非常粗略的基准测试,它显示(至少在我的盒子上)委托实际上比接口更快:
using System;
using System.Diagnostics;
interface IFoo
{
int Foo(int x);
}
class Program : IFoo
{
const int Iterations = 1000000000;
public int Foo(int x)
{
return x * 3;
}
static void Main(string[] args)
{
int x = 3;
IFoo ifoo = new Program();
Func<int, int> del = ifoo.Foo;
// Make sure everything's JITted:
ifoo.Foo(3);
del(3);
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < Iterations; i++)
{
x = ifoo.Foo(x);
}
sw.Stop();
Console.WriteLine("Interface: {0}", sw.ElapsedMilliseconds);
x = 3;
sw = Stopwatch.StartNew();
for (int i = 0; i < Iterations; i++)
{
x = del(x);
}
sw.Stop();
Console.WriteLine("Delegate: {0}", sw.ElapsedMilliseconds);
}
}
结果(.NET 3.5;.NET 4.0b2大致相同):
Interface: 5068
Delegate: 4404
现在我并不特别相信这意味着委托真的比接口更快……但这让我相当确信,它们的速度不会慢一个数量级。此外,这在委托/接口方法中几乎什么也不做。显然,当你每次调用的工作越来越多时,调用成本将会产生越来越小的影响。
需要注意的一件事是,你不能多次创建一个新的委托,因为你只需要使用一个接口实例。如果你使用一个实例方法作为循环中的委托,你会发现在循环外声明委托变量,创建一个委托实例并重用它会更有效。例如:
Func<int, int> del = myInstance.MyMethod;
for (int i = 0; i < 100000; i++)
{
MethodTakingFunc(del);
}
效率比:
for (int i = 0; i < 100000; i++)
{
MethodTakingFunc(myInstance.MyMethod);
}
这会不会就是你看到的问题?
发布于 2010-01-18 06:51:31
我发现委托比虚拟方法快得多或慢得多是完全不可信的。如果说有什么不同的话,那就是代理的速度应该快得可以忽略不计。在较低的级别,委托通常被实现为类似于(使用C风格的表示法,但请原谅任何微小的语法错误,因为这只是一个示例):
struct Delegate {
void* contextPointer; // What class instance does this reference?
void* functionPointer; // What method does this reference?
}
调用代理的工作原理类似于:
struct Delegate myDelegate = somethingThatReturnsDelegate();
// Call the delegate in de-sugared C-style notation.
ReturnType returnValue =
(*((FunctionType) *myDelegate.functionPointer))(myDelegate.contextPointer);
一个类,翻译成C语言,应该是这样的:
struct SomeClass {
void** vtable; // Array of pointers to functions.
SomeType someMember; // Member variables.
}
要调用vritual函数,请执行以下操作:
struct SomeClass *myClass = someFunctionThatReturnsMyClassPointer();
// Call the virtual function residing in the second slot of the vtable.
void* funcPtr = (myClass -> vtbl)[1];
ReturnType returnValue = (*((FunctionType) funcPtr))(myClass);
它们基本上是相同的,除了在使用虚函数时要经过额外的间接层来获取函数指针。然而,这个额外的间接层通常是空闲的,因为现代CPU分支预测器将猜测函数指针的地址,并在查找函数地址的同时推测地执行其目标。我发现(尽管是在D中,而不是在C#中),紧密循环中的虚函数调用并不比非内联的直接调用慢,前提是对于循环的任何给定运行,它们总是解析到相同的真实函数。
发布于 2010-01-18 06:21:45
从CLR v2开始,委托调用的开销与虚方法调用的开销非常接近,虚方法调用用于接口方法。
请参阅Joel Pobar的博客。
https://stackoverflow.com/questions/2082735
复制相似问题