【问题标题】:Is it more efficient to use a flag or an if clause?使用标志或 if 子句更有效吗?
【发布时间】:2013-10-11 18:30:41
【问题描述】:

在 Java 循环中,使用 boolean 标志而不是 if 语句是否更有效?

看看这两段代码。

使用标志:

public boolean isSomethingForAnyone() {
    boolean flag = false;
    for (Item item : listOfItems) {
        flag = flag || item.isSomething();
    }
    return flag;
}

使用if 语句:

public boolean isSomethingForAnyone() {
    for (Item item : listOfItems) {
        if (item.isSomething())
            return true;
    }
    return false;
}

如果isSomething() 在第一次迭代中返回true,那么带有if 语句的方法当然更快。但是,它是平均速度更快还是分支速度减慢到足以使其变慢?另外,如果循环更快,情况会有所不同吗?在这里,为了简单起见,我使用了 for-each 循环,我认为这比使用计数器遍历数组要慢。

【问题讨论】:

  • for-each 并不比普通的 for 循环慢。
  • First 将始终遍历所有元素;当一项为真时,第二个将停止。

标签: java performance if-statement boolean


【解决方案1】:

这两段代码并不完全等价。

即使您只需要调用item.isSomething() 的次数(与我原来的答案相反),第一个版本仍会继续尝试迭代集合的其余部分。

想象一个Item.isSomething() 的实现,它修改了找到该项目的集合(如果它返回true)。那时,第一段代码会抛出一个ConcurrentModificationException,假设它是一个“常规”集合——而第二段代码只会返回true

从根本上说,第二段代码更高效:它只遍历列表中确定答案所需的数量,而不是遍历所有内容。很可能性能不同是无关紧要的——尤其是在集合很小的情况下——但这取决于上下文。

你发现哪个更可读是另一回事 - 效率可能不会很重要,尽管这取决于上下文。就个人而言,我发现第二个版本更易读以及更有效,所以我总是使用它。 (好吧,我会在 if 语句的主体周围添加大括号,仅此而已。)

【讨论】:

  • 我认为标准的编码风格会假设名为isX 的方法是纯粹的,即没有副作用。
  • @selig 是的,我就是这个意思。
  • @selig:是的,对于 us 来说这是一个合理的假设——但优化器做出这样的假设并提前终止循环是不合理的。 (有可能一个非常聪明的 JIT 可以检查这一点,但似乎不太可能。我不认为这是一个很常见的优化情况。)
【解决方案2】:

你是否在做这个循环字面意思十亿次?如果没有,差异可能无法衡量。

您可以查看生成的字节码并确切了解发生了什么,但编译器和 jit 以及 vm 本身可能会优化掉任何差异。

【讨论】:

    【解决方案3】:

    如果您想知道您的机器上哪个“更快”,请运行它。

    如果我们想知道执行哪些指令需要更多指令,那么我们可以查看字节码。

    对于我们得到的第一种方法(调用javap -c

    Code:
       0: iconst_0      
       1: istore_1      
       2: getstatic     #2                  // Field listOfItems:Ljava/util/List;
       5: invokeinterface #3,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
      10: astore_2      
      11: aload_2       
      12: invokeinterface #4,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
      17: ifeq          50
      20: aload_2
      21: invokeinterface #5,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
      26: checkcast     #6                  // class Item
      29: astore_3      
      30: iload_1       
      31: ifne          41
      34: aload_3       
      35: invokevirtual #7                  // Method Item.isSomething:()Z
      38: ifeq          45
      41: iconst_1      
      42: goto          46
      45: iconst_0      
      46: istore_1      
      47: goto          11
      50: iload_1       
      51: ireturn   
    

    我们对循环的内部感兴趣,即第 29-46 行(第 11-26 行是迭代器的东西)。所以大约有 10 条指令。

    对于第二种方法,我们得到这个:

     Code:
           0: getstatic     #2                  // Field listOfItems:Ljava/util/List;
           3: invokeinterface #3,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
           8: astore_1      
           9: aload_1       
          10: invokeinterface #4,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
          15: ifeq          40
          18: aload_1       
          19: invokeinterface #5,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
          24: checkcast     #6                  // class Item
          27: astore_2      
          28: aload_2       
          29: invokevirtual #7                  // Method Item.isSomething:()Z
          32: ifeq          37
          35: iconst_1      
          36: ireturn       
          37: goto          9
          40: iconst_0      
          41: ireturn       
    

    感兴趣的行是 27-37。所以 7 条指令。

    从数字的角度来看,第二种方法排在首位(请注意,我们假设所有堆栈操作都需要相同的时间来执行)。

    【讨论】:

    • 查看字节码并不能反映实际发生了什么,或者真正执行了哪些指令。假设字节码按原样执行,您可能会犯非常非常错误。 JIT 编译器在某些情况下可以优化的效果令人惊讶 - 但如果 JIT 优化 this 代码以使两个循环等效,我会感到惊讶。
    猜你喜欢
    • 1970-01-01
    • 2014-11-25
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-17
    • 2016-08-13
    • 1970-01-01
    相关资源
    最近更新 更多