C# 调用委托与方法的性能
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2082735/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Performance of calling delegates vs methods
提问by Paolo
Following this question - Pass Method as Parameter using C#and some of my personal experience I'd like to know a little more about the performance of calling a delegate vs just calling a method in C#.
按照这个问题 -使用 C# 将方法作为参数传递和我的一些个人经验,我想更多地了解调用委托与仅在 C# 中调用方法的性能。
Although delegates are extremely convenient, I had an app that did lots of callbacks via delegates and when we rewrote this to use callback interfaces we got an order of magnitude speed improvement. This was with .NET 2.0 so I'm not sure how things have changed with 3 and 4.
虽然委托非常方便,但我有一个应用程序通过委托做了很多回调,当我们重写它以使用回调接口时,我们得到了一个数量级的速度提升。这是 .NET 2.0,所以我不确定 3 和 4 有什么变化。
How are calls to delegates handled internally in the compiler/CLR and how does this affect performance of method calls?
在编译器/CLR 内部如何处理对委托的调用,这如何影响方法调用的性能?
EDIT- To clarify what I mean by delegates vs callback interfaces.
编辑- 澄清我所说的委托与回调接口的含义。
For asynchronous calls my class could provide an OnComplete event and associated delegate which the caller could subscribe to.
对于异步调用,我的类可以提供一个 OnComplete 事件和调用者可以订阅的关联委托。
Alternatively I could create an ICallback interface with an OnComplete method that the caller implements and then registers itself with the class that will then call that method on completion (i.e. the way Java handles these things).
或者,我可以使用调用者实现的 OnComplete 方法创建 ICallback 接口,然后将自身注册到类,该类将在完成时调用该方法(即 Java 处理这些事情的方式)。
采纳答案by Jon Skeet
I haven't seen that effect - I've certainly never encountered it being a bottleneck.
我还没有看到这种效果——我当然从未遇到过它成为瓶颈。
Here's a very rough-and-ready benchmark which shows (on my box anyway) delegates actually being fasterthan interfaces:
这是一个非常粗略的基准测试,它显示(无论如何在我的盒子上)委托实际上比接口更快:
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);
}
}
Results (.NET 3.5; .NET 4.0b2 is about the same):
结果(.NET 3.5;.NET 4.0b2 大致相同):
Interface: 5068
Delegate: 4404
Now I don't have particular faith that that means delegates are reallyfaster than interfaces... but it makes me fairly convinced that they're not an order of magnitude slower. Additionally, this is doing almost nothing within the delegate/interface method. Obviously the invocation cost is going to make less and less difference as you do more and more work per call.
现在我并没有特别相信这意味着委托真的比接口快……但这让我相当确信它们并没有慢一个数量级。此外,这在委托/接口方法中几乎没有任何作用。显然,随着每次调用的工作量越来越大,调用成本的差异会越来越小。
One thing to be careful of is that you're not creating a new delegate several times where you'd only use a single interface instance. This couldcause an issue as it would provoke garbage collection etc. If you're using an instance method as a delegate within a loop, you will find it more efficient to declare the delegate variable outside the loop, create a single delegate instance and reuse it. For example:
需要注意的一件事是,您不会在只使用单个接口实例的情况下多次创建新委托。这可能会导致问题,因为它会引发垃圾收集等。如果您在循环内使用实例方法作为委托,您会发现在循环外声明委托变量、创建单个委托实例并重用更有效它。例如:
Func<int, int> del = myInstance.MyMethod;
for (int i = 0; i < 100000; i++)
{
MethodTakingFunc(del);
}
is more efficient than:
比以下更有效:
for (int i = 0; i < 100000; i++)
{
MethodTakingFunc(myInstance.MyMethod);
}
Could this have been the problem you were seeing?
这可能是您看到的问题吗?
回答by Pete Montgomery
Since CLR v 2, the cost of delegate invocation is very close to that of virtual method invocation, which is used for interface methods.
从 CLR v 2 开始,委托调用的成本与用于接口方法的虚方法调用非常接近。
See Joel Pobar's blog.
请参阅Joel Pobar的博客。
回答by dsimcha
I find it completely implausible that a delegate is substantially faster or slower than a virtual method. If anything the delegate should be negligibly faster. At a lower level, delegates are usually implemented something like (using C-style notation, but please forgive any minor syntax errors as this is just an illustration):
我发现委托比虚拟方法快得多或慢得多是完全不可信的。如果有的话,代表的速度应该可以忽略不计。在较低级别,委托通常实现如下(使用 C 风格的符号,但请原谅任何小的语法错误,因为这只是一个说明):
struct Delegate {
void* contextPointer; // What class instance does this reference?
void* functionPointer; // What method does this reference?
}
Calling a delegate works something like:
调用委托的工作方式类似于:
struct Delegate myDelegate = somethingThatReturnsDelegate();
// Call the delegate in de-sugared C-style notation.
ReturnType returnValue =
(*((FunctionType) *myDelegate.functionPointer))(myDelegate.contextPointer);
A class, translated to C, would be something like:
转换为 C 的类将类似于:
struct SomeClass {
void** vtable; // Array of pointers to functions.
SomeType someMember; // Member variables.
}
To call a vritual function, you would do the following:
要调用虚拟函数,您将执行以下操作:
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);
They're basically the same, except that when using virtual functions you go through an extra layer of indirection to get the function pointer. However, this extra indirection layer is often free because modern CPU branch predictors will guess the address of the function pointer and speculatively execute its target in parallel with looking up the address of the function. I've found (albeit in D, not C#) that virtual function calls in a tight loop are not any slower than non-inlined direct calls, provided that for any given run of the loop they're always resolving to the same real function.
它们基本上是相同的,除了在使用虚函数时,您需要通过额外的间接层来获取函数指针。然而,这个额外的间接层通常是免费的,因为现代 CPU 分支预测器会猜测函数指针的地址,并在查找函数地址的同时推测性地执行其目标。我发现(尽管是在 D 中,而不是在 C# 中)紧密循环中的虚函数调用并不比非内联直接调用慢,前提是对于任何给定的循环运行,它们总是解析为相同的实际函数.
回答by Dorian Yeager
What about the fact that delegates are containers? Doesn't the multicast ability add overhead? While we are on the subject, what if we push this container aspect a little further? Nothing forbids us, if d is a delegate, from executing d += d; or from building an arbitrarily complex directed graph of (context pointer, method pointer) pairs. Where can I find the documentation describing how this graph is traversed when the delegate is called?
委托是容器这一事实呢?多播功能不会增加开销吗?当我们讨论这个主题时,如果我们进一步推动这个容器方面呢?如果 d 是委托,则没有什么禁止我们执行 d += d; 或者通过构建一个任意复杂的(上下文指针,方法指针)对的有向图。在哪里可以找到描述调用委托时如何遍历此图的文档?
回答by Paulo Zemek
I did some tests (in .Net 3.5... later I will check at home using .Net 4). The fact is: Getting an object as an interface and then executing the method is faster than getting a delegate from a method then calling the delegate.
我做了一些测试(在 .Net 3.5 中……稍后我将在家里使用 .Net 4 进行检查)。事实是:获取对象作为接口然后执行方法比从方法获取委托然后调用委托要快。
Considering the variable is already in the right type (interface or delegate) and simple invoking it makes the delegate win.
考虑到变量已经是正确的类型(接口或委托),简单地调用它会使委托获胜。
For some reason, getting a delegate over an interface method (maybe over any virtual method) is MUCH slower.
出于某种原因,通过接口方法(可能通过任何虚拟方法)获得委托要慢得多。
And, considering there are cases when we simple can't pre-store the delegate (like in Dispatches, for example), that may justify why interfaces are faster.
而且,考虑到有些情况我们不能简单地预存储委托(例如在 Dispatches 中),这可能证明接口更快的原因。
Here are the results:
结果如下:
To get real results, compile this in Release mode and run it outside Visual Studio.
要获得真正的结果,请在发布模式下编译它并在 Visual Studio 外运行它。
Checking direct calls twice
00:00:00.5834988
00:00:00.5997071Checking interface calls, getting the interface at every call
00:00:05.8998212Checking interface calls, getting the interface once
00:00:05.3163224Checking Action (delegate) calls, getting the action at every call
00:00:17.1807980Checking Action (delegate) calls, getting the Action once
00:00:05.3163224Checking Action (delegate) over an interface method, getting both at every call
00:03:50.7326056Checking Action (delegate) over an interface method, getting the interface once, the delegate at every call
00:03:48.9141438Checking Action (delegate) over an interface method, getting both once
00:00:04.0036530
检查直接呼叫两次
00:00:00.5834988
00:00:00.5997071检查接口调用,每次调用获取接口
00:00:05.8998212检查接口调用,获取一次接口
00:00:05.3163224检查操作(委托)调用,在每次调用时获取操作
00:00:17.1807980检查动作(委托)调用,获取一次动作
00:00:05.3163224检查接口方法上的操作(委托),在每次调用时获取两者
00:03:50.7326056检查接口方法上的操作(委托),获取一次接口,每次调用时委托
00:03:48.9141438检查接口方法上的操作(委托),同时获取一次
00:00:04.0036530
As you can see, the direct calls are really fast. Storing the interface or delegate before, and then only calling it is really fast. But having to get a delegate is slower than having to get an interface. Having to get a delegate over an interface method (or virtual method, not sure) is really slow (compare the 5 seconds of getting an object as an interface to the almost 4 minutes of doing the same to get the action).
如您所见,直接调用非常快。之前存储接口或委托,然后只调用它真的很快。但是必须获得委托比必须获得接口慢。必须通过接口方法(或虚拟方法,不确定)获取委托真的很慢(将获取对象作为接口的 5 秒与执行相同操作以获取操作的近 4 分钟进行比较)。
The code that generated those results is here:
生成这些结果的代码在这里:
using System;
namespace ActionVersusInterface
{
public interface IRunnable
{
void Run();
}
public sealed class Runnable:
IRunnable
{
public void Run()
{
}
}
class Program
{
private const int COUNT = 1700000000;
static void Main(string[] args)
{
var r = new Runnable();
Console.WriteLine("To get real results, compile this in Release mode and");
Console.WriteLine("run it outside Visual Studio.");
Console.WriteLine();
Console.WriteLine("Checking direct calls twice");
{
DateTime begin = DateTime.Now;
for (int i = 0; i < COUNT; i++)
{
r.Run();
}
DateTime end = DateTime.Now;
Console.WriteLine(end - begin);
}
{
DateTime begin = DateTime.Now;
for (int i = 0; i < COUNT; i++)
{
r.Run();
}
DateTime end = DateTime.Now;
Console.WriteLine(end - begin);
}
Console.WriteLine();
Console.WriteLine("Checking interface calls, getting the interface at every call");
{
DateTime begin = DateTime.Now;
for (int i = 0; i < COUNT; i++)
{
IRunnable interf = r;
interf.Run();
}
DateTime end = DateTime.Now;
Console.WriteLine(end - begin);
}
Console.WriteLine();
Console.WriteLine("Checking interface calls, getting the interface once");
{
DateTime begin = DateTime.Now;
IRunnable interf = r;
for (int i = 0; i < COUNT; i++)
{
interf.Run();
}
DateTime end = DateTime.Now;
Console.WriteLine(end - begin);
}
Console.WriteLine();
Console.WriteLine("Checking Action (delegate) calls, getting the action at every call");
{
DateTime begin = DateTime.Now;
for (int i = 0; i < COUNT; i++)
{
Action a = r.Run;
a();
}
DateTime end = DateTime.Now;
Console.WriteLine(end - begin);
}
Console.WriteLine();
Console.WriteLine("Checking Action (delegate) calls, getting the Action once");
{
DateTime begin = DateTime.Now;
Action a = r.Run;
for (int i = 0; i < COUNT; i++)
{
a();
}
DateTime end = DateTime.Now;
Console.WriteLine(end - begin);
}
Console.WriteLine();
Console.WriteLine("Checking Action (delegate) over an interface method, getting both at every call");
{
DateTime begin = DateTime.Now;
for (int i = 0; i < COUNT; i++)
{
IRunnable interf = r;
Action a = interf.Run;
a();
}
DateTime end = DateTime.Now;
Console.WriteLine(end - begin);
}
Console.WriteLine();
Console.WriteLine("Checking Action (delegate) over an interface method, getting the interface once, the delegate at every call");
{
DateTime begin = DateTime.Now;
IRunnable interf = r;
for (int i = 0; i < COUNT; i++)
{
Action a = interf.Run;
a();
}
DateTime end = DateTime.Now;
Console.WriteLine(end - begin);
}
Console.WriteLine();
Console.WriteLine("Checking Action (delegate) over an interface method, getting both once");
{
DateTime begin = DateTime.Now;
IRunnable interf = r;
Action a = interf.Run;
for (int i = 0; i < COUNT; i++)
{
a();
}
DateTime end = DateTime.Now;
Console.WriteLine(end - begin);
}
Console.ReadLine();
}
}
}