【问题标题】:Elegant solution to IF in a loop循环中 IF 的优雅解决方案
【发布时间】:2012-12-15 04:43:58
【问题描述】:

最近我写了这样的东西:

public void doSomething(boolean b1, boolean b2){
   while(true){
      if(b1){
         doThis();
      }
      if(b2){
         doThat();
      }
   }
}

但我真的不喜欢这个解决方案,因为在每次迭代中,您都必须检查 2 个布尔值。因此,作为可能的解决方案,我可以想象在每个循环之前用 ifs 编写 4 个 while 循环,但由于显而易见的原因,这很不利于可维护性。你有什么建议可以让这段代码变得更好更有效吗?

【问题讨论】:

  • 检查这两个布尔值到底有什么问题?
  • 照顾好鼹鼠丘,山会照顾好自己。
  • @Robin:你有没有分析并确定这是一个实际的瓶颈?
  • @Robin:直觉在这些事情上是一个非常糟糕的向导。确保代码正确,取出探查器,然后根据探查器告诉您的内容进行优化。
  • 可以说这不是瓶颈,但它确实给系统带来了额外的压力,在小情况下这无关紧要,但如果这段代码每天执行 12 万亿次,那时间就会增加起来。

标签: java performance optimization


【解决方案1】:

这看起来像是过早的优化。

这是一个思维陷阱。

在您的申请完成并正确之前,不要担心此类细节。然后,如果速度不够快,请拿出一个分析器,看看程序的时间都花在了哪里。

不要浪费脑力去优化可能不引人注目的事情。

【讨论】:

  • 嗯,我认为这与其说是正确的编码风格还为时过早。当有“更正确”的解决方案可用时,没有必要做额外的步骤。记住我们正在编写证明,而不仅仅是代码,这一点很有价值。
  • 谢谢,但我只是想知道是否有人想过。也许已经准备好了解决方案。
  • 哇。不敢相信有人编辑了我的答案以将“废话”更改为“详细信息”。对不起,如果我冒犯了任何人的敏感情感。我知道我们程序员是贵族。
  • 有人编辑,因为这是一个专业的网站,没有必要使用“废话”这个词,而“细节”也可以接受。现在,如果这是一个耕地,“废话”可能是一个更好的词,但我会选择“肥料”
【解决方案2】:

从性能的角度来看,因为 b1b2 没有在方法中修改,所以 branch prediction 和/或 JIT 编译极有可能优化测试,而实际条件检查几乎不会 (如果有的话)惩罚该方法的性能。

如果你有if(getB1()) 而不是if(b1),那么可能还有更多改进的空间。

【讨论】:

  • 谢谢,你能解释一下分支预测是什么意思吗?
  • hmm 你从哪里得到 b1 和 b2 总是有相同的值?
  • 分支预测是编译器的事情。真的,你应该谷歌它,因为我怀疑评论可以完全涵盖它。无论如何,这是您应该在优化之前进行分析的另一个原因——编译器可能会为您解决问题。
  • @Woot4Moo 他的意思是在函数的整个执行过程中,布尔值不会改变。我不认为他是说他们总是对彼此具有同等价值:)
【解决方案3】:

CPU 设计者已经想到了这一点。这称为分支预测。这样做是有效地跳过通常不被调用的分支。这意味着 CPU 将动态删除未使用的分支,几乎不会影响性能。唯一的影响是当预测出错时,因为“回滚”可能很复杂。在你的情况下,布尔值不会改变,所以你不应该看到问题。

【讨论】:

    【解决方案4】:

    我同意这是过早的优化,但如果您的语言对短路评估有保证,则可以使用另一种构造。由于java不允许您将void返回类型转换为boolean,您需要修改doThis()doThat()以返回boolean

    public void doSomething(boolean b1, boolean b2){
        while(true){
            b1 && doThis(),
            b2 && doThat();
        }
    }
    

    (x && foo()) 将仅在 x 的值为 true 时执行该函数,否则将启动短路评估。

    您必须非常小心,您的编译器不会仅仅优化整个表达式,因为实际上没有分配任何值。

    在所有情况下都可以避免两种比较的真正可能的优化是在内部循环中使用 switch 语句。

    public void doSomething(boolean b1, boolean b2){
        int state = (b1 ? 1 : 0) + (b2 ? 2 : 0);
        while(true){
            switch (state){
                case 1: doThis(); break;
                case 3: doThis();
                case 2: doThat();
                default:
            }
        }
    }
    

    【讨论】:

    • 假设doThis 返回一个布尔值。
    • 我认为,这在大多数情况下并不实用,但这对我来说似乎很聪明。真是个好主意。
    • 老实说,我看不出if(b1) doThis();b1 && doThis(); 之间在性能方面的区别——在这两种情况下都会评估布尔值。
    • @assylias:我同意。我提出这更多是作为 OP 代码简洁表达的一种可能性。
    • 我同意@assylias。我很高兴您接受了这个答案,但请不要在实际代码中使用它。你的同事会感谢你的。
    【解决方案5】:
    public void doSomething(boolean b1, boolean b2){
       while(true){
          if(b1 && b2)  
          {  
            doThis();  
            doThat();  
          }
          else if(b1){
             doThis();
          }
          else if(b2){
             doThat();
          }
    
       }
    }
    

    编译器将对此进行优化,并且不会在循环中遇到每个 if / else if 块。类似于 switch 语句。回顾一下潜在的逻辑,如果 b1b2 是真的,你想要 doThisdoThat。否则只有一个是正确的,并且逻辑与以前相似。

    【讨论】:

    • 这并不能真正解决任何问题,因为 doThat 只会在 b1 评估为 false 时触发。 Robin 的解决方案有可能在适当的条件下同时开火。
    • 我认为这可能无法满足该代码的业务需求。由于该问题并未表明这两个布尔值的值是否相互排斥。也许它们都是为了不同的目的,可以是对的也可以是错的
    • 这里改了更符合原来的业务需求。
    • @Perception 是的,我现在看到了,因此我已经修改了我的。
    • @Woot4Moo 谢谢,但这是重复代码。在这个小例子中,这些只是 2 行。在现实生活中的例子中,这可能更多。然后就不好过了。
    【解决方案6】:

    我猜你可以将 if 语句移到 while 之外,像这样

    if (b1) {
        while (true) {
            doThis();
        }
    }
    if (b2) {
        while (true) {
            doThat();
        }
    }
    

    但是您实际上并没有获得任何好处,因为您仍然需要评估使 while 循环重复的条件,并且您必须执行两倍于原始代码的频率。

    【讨论】:

    • 这不再是同一个程序了。
    猜你喜欢
    • 1970-01-01
    • 2018-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-31
    • 2011-02-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多