案例标签的顺序对switch语句的效率有多大影响?

时间:2009-12-01 16:41:43

标签: c# c++ c performance conditional-statements

考虑:

if (condition1)
{
    // Code block 1
}
else
{
    // Code block 2
}

如果我知道condition1大部分时间都是true,那么我应该按编写逻辑编码,而不是:

if (!condition1)
{
    // Code block 2
}
else
{
    // Code block 1
}

因为我将避免jump对第二个代码块的惩罚(注意:我对汇编语言的了解有限)。这个想法是否延续到switch语句和case标签?

switch (myCaseValue)
{
    case Case1:
        // Code block 1
        break;

    case Case2:
        // Code block 2
        break;

    // etc.
}

如果我知道其中一个案例会更频繁发生,我可以重新排列case标签的顺序,以便更有效吗?我是不是该?在我的代码中,我一直在按字母顺序排列案例标签以获得代码可读性,而没有真正考虑它。这是微观优化吗?

9 个答案:

答案 0 :(得分:33)

x86或x86_64等现代硬件的一些事实:

  • 除了解码之外,无条件采用的分支几乎没有额外的成本。如果你想要一个数字,它大概是四分之一个时钟周期。
  • 正确预测的条件分支几乎没有额外费用。
  • 未正确预测的条件分支的惩罚等于处理器流水线的长度,这大约是12-20个时钟,具体取决于硬件。
  • 预测机制非常复杂。迭代次数较少的循环(在Core 2上,例如最多64次)可以完美预测。如果它们不太长,可以预测像“采取采取未采取”这样的小重复模式(Core2上的IIRC 6)。

您可以在Agner Fogs优秀manual.

中阅读有关分支预测的更多信息

Switch语句通常由编译器的跳转表替换。在大多数情况下,案件的顺序根本不会产生任何影响。间接跳跃也有预测机制。

所以问题不在于你是否更有可能进行跳转,如果它们是可预测的,至少对于你打算运行代码的硬件来说是这样。这根本不是一个简单的问题。但是如果你有一个取决于随机(或伪随机)条件的分支,你可以尝试将其重新表述为无分支语句。

答案 1 :(得分:7)

关于if语句的结论在我熟悉的大多数硬件上都不会出现。问题不在于你在跳跃,而在于你正在分支。代码可以采用两种不同的方式,具体取决于比较结果。这可能会阻止大多数现代CPU上的管道。分支预测很常见,并且在大多数情况下修复了问题,但与您的示例无关。预测器同样可以预测比较将是错误的,因为它可能是真的。

像往常一样,请参阅维基百科:Branch Predictor

答案 2 :(得分:6)

这取决于。编译器将使用一组内部依赖于实现的标准来决定是将switch实现为if-like测试序列还是跳转表。例如,这可能取决于您的case标签集的“紧凑”程度。如果您的case标签值形成“密集”集,则编译器可能更有可能使用跳转表,在这种情况下,case标签的排序无关紧要。如果它决定使用类似于if-else测试的序列,那么顺序可能很重要。

请注意,switch的正文是一个大型语句,case标签为该语句提供了多个入口点。出于这个原因,编译器在该语句中重新排列case“子块”的能力(以及你的能力)可能是有限的。

答案 3 :(得分:3)

为了便于阅读,应以最有效的方式订购案例标签。

重新排序案例标签以提高效率是一个过早优化的情况,除非分析器明确告诉您这是一个问题。

答案 4 :(得分:3)

我认为,即使是最初的前提 - 您可以通过重新排列条件来优化if语句也可能是错误的。在非优化构建中,您可能会发现正在进行的操作具有一定的价值 - 也许。在一般情况下,对于任何一种情况,你将不得不至少跳过一次,因此无论如何都没有优势(通常)安排条件。但这是针对非优化版本的,那么谁关心这种优化呢?

在优化版本中,我认为您可能对编译器有时为if语句生成的内容感到惊讶。编译器可以将一个或另一个(或两个)情况移动到某个外部的某个地方。我认为你试图通过玩“先到先得”这个条件来天真地优化它并不一定能达到你想要的效果。最好只在检查编译器生成的内容后才能执行此操作。当然,这成为一个昂贵的过程,因为即使您对语句做出的最轻微的更改也可能会改变编译器决定生成输出代码的方式。

现在,就switch语句而言,当代码更易读时,我总是使用switch。编译器应该使用等同于switch语句的if语句执行的最差操作是生成相同的代码。对于多种情况,switch语句通常被编译为跳转表。但是,再一次将一个变量与一组值进行比较的if测试可能会很好地被编译器识别,这样它就会做同样的事情。但是,我猜测使用开关可以让编译器更容易识别出这种情况。

如果您真的对充分利用该条件的性能感兴趣,可以考虑使用类似MSVC's Profile Guided Optimization(PGO或'pogo')的内容,它使用分析运行的结果来优化条件的运行方式得到生成。我不知道海湾合作委员会是否有类似的能力。

答案 5 :(得分:2)

我不确定C#编译器,但我知道在汇编中,switch语句实际上可以编程为跳转到特定行,而不是像if语句那样计算表达式。因为在一个select中你有所有常量,它只是将case作为行号处理,你直接跳转到没有任何评估的传入的行号(case值)。这使得案例陈述的顺序根本不重要。

答案 6 :(得分:1)

我假设您知道只有这是一个热点才有意义。判断它是否是热点的最佳方法是运行代码,对程序计数器进行采样,并查看它是否存在超过10%的时间。如果是热点,请查看执行ifswitch所花费的时间。通常它可以忽略不计,除非您的Block 1和/或Block 2几乎没有。您可以使用分析器。我只是反复暂停。

如果您不熟悉汇编语言,我建议您学习它,足以理解编译器生成的内容。这很有趣而且不难。

答案 7 :(得分:0)

正如其他人所说,这取决于很多事情,包括有多少案例,如何进行优化,以及你正在运行的架构。有关一个有趣的概述,请参阅http://ols.fedoraproject.org/GCC/Reprints-2008/sayle-reprint.pdf

答案 8 :(得分:-2)

如果你把最常发生的情况放在首位,这将稍微优化代码,并且由于切换器状态的工作方式同样如此。当程序进入切换并找到一个真实的情况时,它将执行它并命中break,这将退出循环。你的想法是正确的。

但是,我确实认为这种优化是非常小的,如果它减慢了你的开发时间,那么它可能不值得。此外,如果您必须大幅修改您的程序流程以适应这一点,它可能不值得。你最多只能节省几个周期,可能永远不会看到改进。