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,但我认为语言无关紧要。

4

10 回答 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 ) 比较的一系列比较。

于 2008-10-05T23:56:13.000 回答
10

好吧,我相信几乎所有时候,例如,具有数字排序值的易读性将覆盖您通过减少比较指令的数量可能获得的任何微小好处。

话虽如此,与所有优化一样:

  1. 让它起作用
  2. 测量它
  3. 如果它足够快,别管它
  4. 如果它太慢,那么优化它

哦,我可能会从一开始就使用开关/外壳!;-)

于 2008-10-05T23:26:57.003 回答
7

发生这种情况的一个经典案例(在您的帖子中实际上有 5 个选项)是在 ffmpeg 中的 decode_cabac_residual 函数中。这是相当重要的,因为分析(非常重要——不要在分析之前优化!)表明它占 H.264 视频解码所用时间的 10-15% 以上。if 语句控制一组语句,这些语句对要解码的各种残差类型的计算方式不同——不幸的是,如果函数对 5 种类型中的每一种重复 5 次,则会由于代码大小而损失太多速度剩余的。因此,必须使用 if 链。

对许多常见的测试流进行了分析,以根据可能性对它们进行排序;顶部是最常见的,底部是最少的。这给出了小的速度增益。

现在,在 PHP 中,我怀疑您在 C 中获得的低级样式速度增益要少得多,如上例所示。

于 2008-10-05T23:26:01.610 回答
2

使用 switch/case 语句绝对是这里的方法。

这使编译器(解释器)有机会利用跳转表到达正确的分支,而无需进行 N 次比较。想象一下它创建了一个索引为 0、1、2、.. 的地址数组,然后它可以在一次操作中在数组中查找正确的地址。

另外,由于 case 语句中的语法开销较小,因此它也更容易阅读。

更新:如果比较适用于 switch 语句,那么这是配置文件引导优化可以提供帮助的领域。通过运行具有实际测试负载的 PGO 构建,系统可以生成分支使用信息,然后使用它来优化所采用的路径。

于 2008-10-05T23:57:52.807 回答
1

如果代码必须进行额外的测试,那么它肯定会运行得更慢。如果在这部分代码中性能很重要,那么您应该将最常见的情况放在首位。

当您不确定性能是否足够快时,我通常同意“测量,然后优化”方法,但是如果代码只需要尽可能快地运行并且修复就像重新安排测试一样简单,那么我现在要快速编写代码,并在您上线后进行一些测量,以确保您的假设(例如 3 将在 80% 的时间发生)实际上是正确的。

于 2008-10-05T23:36:39.013 回答
1

我不会回答 PHP 问题,而是更笼统地回答一下。它并不直接适用于 PHP,因为它会经过某种解释。

如果需要,许多编译器可以在 if-elif-elif-... 块之间进行转换以切换块,并且 elif 部分中的测试足够简单(并且其余的语义恰好是兼容的)。对于 3-4 次测试,使用跳表不一定能获得任何好处。

原因是 CPU 中的分支预测器非常擅长预测会发生什么。实际上,唯一发生的事情是对指令获取的压力有点高,但它几乎不会是惊天动地的。

但是,在您的示例中,大多数编译器会识别 $var 是常量 3,然后在 if..elif.. 块中将 $var 替换为 3。这反过来又使表达式保持不变,因此它们被折叠为真或假。所有的假分支都被死代码消除器杀死,并且对真的测试也被消除了。剩下的是 $var == 3 的情况。不过你不能依赖 PHP 这么聪明。一般来说,您不能进行 $var 的传播,但它可能来自某些调用站点。

于 2008-10-06T02:51:23.550 回答
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]->();
}
于 2008-10-06T18:37:08.103 回答
0

对于纯粹是平等分析的代码,我会将其移至开关/案例,因为这样可以提供更好的性能。

$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{
 }

现在,如果您进行更复杂的比较,我会将它们嵌套在开关中,或者只使用 elseif。

于 2008-10-06T02:12:59.150 回答
0

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

于 2008-10-06T02:16:58.970 回答
0

只有您可以判断优化顺序或将其重新排列为实际上是二叉树的性能差异是否会产生显着差异。但我怀疑你必须每秒有数百万次,而不是数千次,才能在 PHP 中思考它(在其他一些语言中更是如此)。

计时。看看你每秒可以运行多少次 if/else if/else 语句而不采取任何行动并且 $var 不是选项之一。

于 2008-10-06T02:35:35.843 回答