Func还是Action比if-else或switch-case更快?

时间:2014-07-02 22:36:12

标签: c#

我正在实现业务逻辑,其中比较和代码执行必须快速。假设我有1000个案例的开关盒。像这样的东西

    public void DoWork(int num)
    {
        switch (num)
        {
            case 1:
                {
                    function_1();
                    break;
                }
            case 2:
                {
                    function_2();
                    break;
                }
            //.
            //.
            //.
            //.
            //.
            //UPTO 1000 CASES

            case 1000:
                {
                    function_1000();
                    break;
                }
        }
    }

现在如果我使用Func或Action而不是ABOVE代码,它会不会更快?请参阅下面的代码

    //This array will be initialized previously
    Action[] actions = { new Action(function_1), new Action(function_2), /*....UPTO 1000....*/ new Action(function_1000) };
    public void DoWork(int num)
    {
        actions[num]();
    }

现在请告诉我哪一个会更快,为什么?

由于

2 个答案:

答案 0 :(得分:5)

优化编译器已经知道如何使用计算分支来实现switch语句,其中控制变量是一个整数类型,并且大小写是顺序的。实际上,这意味着编译器已经生成了一个函数指针表。

交换机版本的一个优点是被调用的函数如果足够小就可以内联,删除函数调用(比本地分支稍贵)。内联还可以进一步优化。出于这个原因,编译器有时也会将间接调用转换为条件分支,这种优化称为 devirtualization

C#没有给你这个优化,但.NET JIT编译器可能会。它在很大程度上取决于架构(x86,x64,ARM或Itanium)和版本,您无需提及。并非如果这些功能正在做任何非平凡的事情,那么会有很大的性能差异。

底线与所有未充分描述的效果问题相同 - 除非您有剖面仪证据证明这是性能瓶颈,否则您应该做更易于维护的事情。

答案 1 :(得分:0)

虽然我同意带有1000个选项的switch语句对于生产代码几乎肯定不是一件好事,并且肯定很难维护,但这个问题对于switch语句和Action代理的性能是有价值的。

请考虑以下情况(我曾多次遇到这种情况,这导致我提出这个问题。)

  • 我正在建造一个班级。
  • 构造函数可以接收多个选项之一。
  • 一旦我有一个班级的实例,我需要在课堂上调用一个方法1000万次。

在这种情况下关于switch语句或委托(Action / Func)是否会表现更好的问题变得相关

具体来说,我可以在我的类方法中放置一个switch语句来处理各种场景,或者我可以在构造时分配适当的委托,这样我的类方法只需要调用预先指定的委托

现在,我可能在构造期间需要一个switch语句来分配委托,但我只需要这样做一次。

然后问题是:运行一千万次切换语句或者调用委托方法(作为Action或Func)一千万次会更快吗?

这与OP的问题不完全相同,但我们中的许多人更有可能遇到。

许多人会马上说代表的速度更快,因为一旦定义它只是一个方法调用,它比switch语句快......

......或者是吗?

正如许多其他StackOverflow答案中所提到的,交换机优化了一些。对于大量选项,它本质上是一个哈希集或字典。换句话说,非常快。

但方法调用有多快?嗯,它肯定比没有方法调用慢。

我不够聪明地查看IL代码并通过目视检查推断出一组指令是否比另一组更快......

所以我测试了它!

在进入我刚刚描述的场景之前,我首先会针对OP场景展示我的结果 ...但是在交换机中有50个选项而不是1000个。(复制和粘贴太多了!:)

我得到了与OP不同的结果。简而言之,基于完全场景并确保我在比较苹果和苹果,我确定了代表方法更快

OP's Scenario:
+---------------------+------------+-----------+
|      TEST NAME      | ITERATIONS | Time (ms) |
+---------------------+------------+-----------+
| Baseline            | 10 million |        67 |
| Switch (50 options) | 10 million |       258 |
| 50 Action Delegates | 10 million |       191 |
+---------------------+------------+-----------+
  1. 基线测试 - 基线测试执行1000万次简单的整数添加操作。
  2. OP的交换机(50) - 在此测试中,我们将DoWork方法称为1000万次。 DoWork方法包含一个带有50个选项的switch语句,每个选项都调用另一个方法来执行简单的整数添加。 (就像我说的,我同意这种设计不是最佳的。)
  3. OP的方法代表(作为动作) - 在此测试中,我们将DoWork方法称为1000万次。 DoWork方法从预初始化的数组中调用特定的Action委托。委托执行了一个简单的整数添加。
  4. 结果:我实际上预计委托技术在这种情况下会更快,并且是191ms到258ms。我们在switch语句中有一个额外的步骤,因为它正在评估交换机并且仍在进行方法调用。但这就是问题的定义方式。而且,正如其他人所描述的那样,架构和内部优化可能意味着一台机器的性能与另一台机器的性能不同。

    观点:结果有25%的差异。但是,让我们说清楚。 非常很少有这种差异很重要的场景。这种情况下的两种技术都在快速尖叫。

    现在,让我们回到我的场景。

    My Scenario 
    +---------------------+------------+-----------+
    |      TEST NAME      | ITERATIONS | Time (ms) |
    +---------------------+------------+-----------+
    | Switch (50 options) | 10 million |       192 |
    | One Action Delegate | 10 million |        86 |
    +---------------------+------------+-----------+
    
    1. 我的交换机(50) - 在此测试中,我将方法DoWork调用了1000万次。内部DoWork是一个包含50个选项的switch语句。每个选项都会添加一个简单的整数内联。
    2. 我的单个Action代理 - 在此测试中,我选择了一个方法委托,并将其称为1000万次。在委托内部执行简单的整数添加。
    3. (如果您已经忘记了,请重新阅读以上内容,以便记住我的情景以及我为何比较这两项测试。)

      结果: - 同样,我希望单个委托调用比使用内联代码评估switch语句更快。尽管如此,我还是不确定。现在我。这告诉我的是:

      1. 如果我的代码可以多次调用(数千或数百万次),则调用单个预定义委托方法的速度几乎比使用switch语句快3倍。
      2. 我再也不用担心这个了,因为两种方法都非常快。
      3. TAKEAWAY:通过实际测量行为,我们现在有了一组可靠的数字而不仅仅是推测。也许在不同的机器上数字会有所不同,但我们仍然可以看到,对于已经非常快的东西,差异很小。

        总之,我将回应其他一些答案:

        在考虑是否使用switch语句或方法委托时(有几种合法的情况,您可能会面临这种选择),如果您只关心几毫秒,则只需要担心性能。否则,请使用保持代码清洁和可维护的任何内容。