你有多经常担心需要处理多少个if条件语句?

4
如果您有以下内容:
$var = 3; // we'll say it's set to 3 for this example
if ($var == 4) {
    // do something
} else if ($var == 5) {
    // do something
} else if ($var == 2) {
    // do something
} else if ($var == 3) {
    // do something
} else {
    // do something
}

如果说80%的时间$ var 是3,你是否担心在找到真实情况之前,它会经过4个if语句?
我认为在小网站上这不是什么大问题,但是当该if语句每秒运行1000次时怎么办?
我正在使用PHP工作,但我觉得语言并不重要。
10个回答

13

这是我曾经为雷达系统编写软件时所采用的方式。(速度在雷达中非常重要。这是少数几个“实时”确实意味着“真实”而不是“快速”的领域之一。)

[我将切换到Python语法,这对我来说更容易,相信您也可以理解。]

if var <= 3:
    if var == 2:
        # do something
    elif var == 3:
        # do something
    else: 
        raise Exception
else:
    if var == 4:
        # do something
    elif var == 5:
        # do something
    else:
        raise Exception

你的if语句形成了一棵树,而不是一个平面列表。随着你向这个列表中添加条件,你会摇晃树的中心。平面的n次比较平均需要n/2步。而树则需要log(n)次比较。


非常好的想法。我知道以前做过类似的事情,但也许有些东西我更经常使用。 - Darryl Hein
哈哈,把它改成“var <=3”,否则“elseif var == 3”永远不会被满足!我希望你没有直接从你的雷达代码中复制这个。;-) - asterite

10

我认为,几乎所有时候,例如使用数字排序值的易读性,都会胜过通过减少比较指令所获得的微小好处。

话虽如此,但对于所有优化:

  1. 让它工作起来
  2. 测量它
  3. 如果足够快,则不要修改
  4. 如果太慢,那么再进行优化

哦,我可能会一开始就使用 switch/case!;-)


是的,你说得对,使用 switch 会更好,但我正在考虑更大范围的可能性 if 语句。 - Darryl Hein

7
这是一个经典案例(就像您发布的帖子中所提到的那样)出现在ffmpeg中的decode_cabac_residual函数中。这非常重要,因为分析(很重要 - 不要在分析之前进行优化!)显示它占用了H.264视频解码时间的10-15%以上。if语句控制一组语句,其针对要解码的各种类型残差进行不同的计算 - 遗憾的是,如果将该函数复制5次以处理5种类型的残差,则会由于代码大小而失去太多速度。因此,必须使用if链。

对许多常见测试流进行了分析以按可能性排序;顶部是最常见的,底部是最少的。这使得速度略有提高。

现在,在PHP中,我怀疑低级风格速度增益要少得多,就像上面的例子一样。


2

使用switch/case语句无疑是此处的最佳选择。

这样做可以使编译器(解释器)有机会利用跳转表在不进行N次比较的情况下找到正确的分支。 可以将其视为创建一个地址数组,索引为0,1,2,...然后它只需在单个操作中查找正确的地址。

此外,由于case语句中的语法开销较小,因此读起来更容易。

更新:如果比较适合于switch语句,则这是一个可以通过基于剖面的优化来帮助的领域。 通过使用真实测试负载运行PGO构建,系统可以生成分支使用信息,然后使用此信息来优化所采取的路径。


我更多地是在一般性思考,但如果情况就像示例中那样,我肯定会使用 switch。 - Darryl Hein

1

如果代码需要进行额外的测试,那么它肯定会变慢。 如果性能在此代码段中至关重要,则应首先放置最常见的情况。

当您不确定性能是否足够快时,我通常赞同“先测量,再优化”的方法,但如果代码只需要尽可能快地运行,并且修复问题很容易,例如重新排列测试,则我会立即使代码更快,并在您上线后进行一些测量以确保您的假设(例如,3会发生80%的时间)实际上是正确的。


1

我不仅仅回答PHP问题,我会更一般地回答。它不直接适用于PHP,因为它将通过某种解释进行。

许多编译器可以在需要时将if-elif-elif-...块转换为switch块,并且elif部分中的测试足够简单(并且其余语义恰好兼容)。对于3-4个测试,使用跳转表可能没有任何收益。

原因是CPU中的分支预测器非常擅长预测发生的情况。实际上,唯一发生的事情就是指令获取的压力稍微增加了一点,但这几乎不会引起轰动。

然而,在您的示例中,大多数编译器都会认识到$var是一个常量3,然后在if..elif..块中将$var替换为3。这反过来使表达式变为常量,因此它们被折叠为true或false。所有false分支都被死代码消除器杀死,true的测试也被消除。剩下的是$var == 3的情况。但是,您不能依赖PHP那么聪明。通常情况下,您无法传播$var,但从某些调用站点可能是可能的。


这突显了使用示例代码而非真实世界代码的问题。我相信变量的恒定性在概念上不是原始“问题”的一部分;那将是荒谬的。它肯定会在某种程度上受到用户输入的影响,但不完全是“随机”的。 - Bobby Jack

1
你可以尝试使用代码块数组,然后调用它们。这样所有的代码块都有相同的开销。

Perl 6:

our @code_blocks = (
  { 'Code Block 0' },
  { 'Code Block 1' },
  { 'Code Block 2' },
  { 'Code Block 3' },
  { 'Code Block 4' },
  { 'Code Block 5' },
);

if( 0 <= $var < @code_blocks.length ){
  @code_blocks[$var]->();
}

0

如果代码纯粹是等式分析,我会将其移动到 switch/case 中,因为这样可以提供更好的性能。

$var = 3; // we'll say it's set to 3 for this example
switch($var)
 {
   case 4:
      //do something
      break;
   case 5:
      //do something
      break;
   case:
      //do something when none of the provided cases match (same as using an else{ after the elseif{
 }

如果你正在进行更复杂的比较,我建议将它们嵌套在switch语句中,或者只是使用elseif。


0
在面向对象的语言中,如果一个选项提供了大量的if语句,那么这意味着你应该将行为(例如,你的//做某事代码块)移动到包含该值的对象中。

0

只有您能确定优化顺序或重新排列以实际上成为二叉树的性能差异是否会产生显着差异。但我怀疑,您必须每秒执行数百万次,而不是数千次,才会考虑在PHP中(甚至更多的其他语言中)思考它。

计时。查看您可以运行上述if / else if / else语句而不采取任何操作并且$var不是其中之一的次数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接