【问题标题】:Switch() { case: } performance in C [duplicate]Switch() { case: } C 中的性能 [重复]
【发布时间】:2012-09-13 21:43:50
【问题描述】:

可能重复:
Why Switch/Case and not If/Else If?

我想了解 C 中的 switch() case: 语句如何 由编译器翻译成汇编操作码。

具体来说,我有兴趣了解其中的区别 带有一系列if then else 分支。

性能比较是主要话题。

关于词汇的几句话: 我熟悉汇编程序的主要概念,很久以前就为更简单的系统编写过汇编程序,但现在肯定对 x86 汇编程序语义一无所知。 所以直接汇编输出将没有用。 更喜欢伪代码。

【问题讨论】:

  • 写点东西然后在ollydbg中打开,这样才能真正理解它。
  • 这里有很多很棒的答案。很难选择一个......
  • 我不一定同意对这个问题施加的“重复”评论。虽然这两个问题确实有一些共同点,但前一个问题更广泛(为什么使用 switch/case ?),因此可以在许多问题上得到答案,例如更简洁的代码、更易于阅读、维护等。我的问题专门针对编译器的汇编程序翻译,以及对性能的预期影响。一个更精确的范围,在这里得到了正确的回答。

标签: c performance switch-statement


【解决方案1】:

编译器可以决定将其实现为等效的if/else if 语句系列,或者它可以决定使用branch table 对其进行优化。这取决于几个参数,例如分支的数量和包含您检查的所有值的最小范围的大小。

更新:我记得在某处读过,通常编译器不会费心创建分支表,除非至少有 4 个或更多开关案例; Stephane Rouberol 的informative comment 下面专门记录了如何为 GCC 配置此阈值。

【讨论】:

  • 而“分支表”是?条件跳转“对”?
  • @KirilKirov:刚刚添加了相关链接。
  • 来自 gcc v4.7.2 发行说明:添加了对新参数 --param case-values-threshold=n 的支持,以允许用户控制执行 switch 语句之间的截止作为一系列 if 语句并使用跳转表。
  • 谢谢。 +1,因为我打算回答完全相同。
  • @StephaneRouberol:感谢您的评论,我将其合并到答案中以获得更好的可见性。
【解决方案2】:

常见的回答总是“取决于”。 性能可能取决于平台/CPU 类型、编译器、编译器选项等。

我确实认为,在适当的情况下,switch() 构造将具有复杂性 log(n),其中 n 是 case: 语句的数量。这是通过“二分搜索”实现的。

This page 有很多细节(专注于微软的编译器,但我认为普遍适用的一般想法)。

【讨论】:

  • 在适当的情况下,switch 的复杂度肯定是 O(1)。
  • 对,如果案例是一个没有“漏洞”的序列......我想。否则你将不得不搜索。
  • 我的意思是,如果有跳转表,最好的情况是 O(1)。
  • 即使有漏洞,也可以通过跳转到“默认”标签或交换机末尾(如果适用)来填充跳转表中的空位。这更多的是整个范围有多大(以及跳转表有多大)的问题,而不是人口是否密集的问题。
  • 对,但这需要“案例”标准按顺序排列,不是吗?
【解决方案3】:

根据编译器中的各种启发式方法,生成的代码最终可能只是一个简单的“if else if”语句链。在某些情况下case空间较小的情况下,编译器可以做一个跳转表,例如:

switch (foo) {
case 0:
    a();
case 1:
    b();
case 2:
    c();
case 3:
    d();
default:
    e();
}

可以翻译成这样的:

if (foo < 0 || foo > 3) goto label_default;
else goto internal_jump_table[foo];

internal_jump_table = { label_0, label_1, label_2, label_3 };
label_0: a();
label_1: b();
label_2: c();
label_3: d();
label_default: e();

还有其他可以做的优化。编译器可以构建 if 语句的层次结构来二进制搜索正确的值,而不是检查相等性。或者你可能有一堆适合跳转表的值和一些完成正常搜索的异常值。或者可能只是两个跳转表。

【讨论】:

  • 哇,我不知道可以使用您的示例 C 语义构建自己的跳转表。看起来不错
  • @Cyan 不是。这只是伪代码。有一个 gcc 扩展来获取标签地址并对其执行 goto,但语法与我写的不同。
  • arf,好得令人难以置信...无论如何,您的伪代码清楚地表达了您的解释。谢谢
  • 一个复杂的问题:假设,作为您的示例,我有一个具有 4 个值 {0、1、2、3} 的开关。让我们假设我可以保证只提供自动值。有没有办法避免在跳跃之前执行的健全性检查?问候
  • 我知道在过去,如果您打开枚举并处理所有情况,gcc 至少会跳过检查,而是做一些更有效的事情。但是现在摆弄它,我无法让 gcc 不检查(尽管我过去使用了不同的处理器架构)。顺便提一句。生成跳转表的下限好像是5个case,4个就是做二分查找。
【解决方案4】:

考虑到“足够大”的数量switch cases(足以让编译器选择生成分支表而不是简单的if / else if):

  • 一个 switch-case 将有恒定时间 (O(1)) 访问要执行的正确代码块,

  • 而一系列if/else if 语句将具有线性时间 (O(n) 其中 n 是要评估的条件数 (if 语句), 对于 n >= "足够大")

更新:当然这些考虑没有考虑编译器优化!

【讨论】:

  • if 实现应该有 O(log(n)) 时间,因为人们会用二分搜索(或等效的方法)来实现测试;许多机器产生的比较结果允许三向搜索区别:小于、等于、大于)。
  • mmm...不确定,我认为编译器无法在 if 语句的条件评估之间进行语义比较
  • 当然可以。开关的控制表达式是一个整数类型,所以它是有序的。
  • 好吧,对于 switch 语句,它甚至可以比二分查找(分支表)做得更好,但对于条件表达式可能彼此不相关的 if 语句则不行。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-08-23
  • 2016-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多