为了账号安全,请及时绑定邮箱和手机立即绑定

调用代表与方法的性能

/ 猿问

调用代表与方法的性能

接下来的问题- 使用C#将方法作为参数传递,以及我的一些个人经验,我想了解更多有关调用委托的性能而不是仅使用C#调用方法的性能。


尽管委托非常方便,但我有一个应用程序通过委托进行了许多回调,当我们重写该程序以使用回调接口时,速度得到了改善。这是.NET 2.0的版本,所以我不确定3和4的变化。


如何在编译器/ CLR中内部处理对委托的调用,这如何影响方法调用的性能?


编辑 -为了澄清我的意思是委托与回调接口。


对于异步调用,我的类可以提供OnComplete事件和调用者可以预订的关联委托。


或者,我可以用调用者实现的OnComplete方法创建一个ICallback接口,然后向该类注册自己,该类将在完成时调用该方法(即Java处理这些事情的方式)。


查看完整描述

3 回答

?
HUX布斯

我还没有看到这种效果-我当然从来没有遇到过瓶颈。


这是一个非常粗糙的基准,它(无论如何在我的盒子上)显示了委托实际上比接口要快:


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

}

难道这就是您所看到的问题?


查看完整回答
反对 2019-12-26
?
慕斯709654

我发现委托实际上比虚拟方法更快或更慢是完全不可行的。如果有的话,代表的速度应该可以忽略不计。在较低的级别上,代理通常实现类似(使用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.

}

要调用虚拟函数,请执行以下操作:


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#)紧密循环中的虚函数调用并不比非内联直接调用慢,只要对于循环的任何给定运行,它们始终解析为相同的实函数。


查看完整回答
反对 2019-12-26
?
倚天杖

我做了一些测试(在.Net 3.5中...稍后我将使用.Net 4在家里进行检查)。事实是:获取对象作为接口然后执行该方法比从方法获取委托然后调用委托要快。


考虑到变量已经是正确的类型(接口或委托),简单地调用它会使委托获胜。


由于某种原因,通过接口方法(也许通过任何虚拟方法)获得委托要慢得多。


而且,考虑到某些情况下我们无法简单地预先存储委托(例如,在Dispatches中),这可以证明为什么接口更快。


结果如下:


要获得真实的结果,请在“发布”模式下进行编译,然后在Visual Studio外部运行它。


两次检查直拨电话

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


如您所见,直接调用的速度非常快。之前存储接口或委托,然后仅调用它确实非常快。但是,必须获得委托比必须获得接口要慢。必须通过接口方法(或不确定的虚拟方法)获得委托确实很慢(将获取对象作为接口的5秒与执行该操作所需的近4分钟进行比较)。


产生这些结果的代码在这里:


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

        }

    }


}


查看完整回答
反对 2019-12-26

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信