我应该最小化“if”语句或“for”循环的数量吗?

时间:2013-10-07 17:39:01

标签: java performance if-statement for-loop

我有一个对象列表,需要对每个元素应用多个有条件应用的操作。采用“if-for”方法或“for-if”方法更有效率。换句话说,我应该最小化if语句的数量还是for循环的数量?这有标准吗?

确定这个的可靠方法是什么?

“If-For” 接近最小化if语句

public void ifForMethod() {
    if (conditionA) {
        for (Object o : listOfObjects) {
            doA(o);
        }
    }

    if (conditionB) {
        for (Object o : listOfObjects) {
            doB(o);
        }
    }
}

“For-If” 最小化for循环的方法

public void forIfMethod() {
    for (Object o : listOfObjects) {
        if (conditionA) {
            doA(o);
        }
        if (conditionB) {
            doB(o);
        }

    }
}

假设

  • 条件是简单的布尔值,迭代时不会改变。
  • 一个或多个条件都是真的。 (有两个以上的条件)
  • 每种情况都与其他条件无关。
  • 内部方法根本不会发生冲突或相互影响。它们执行的顺序无关紧要。

10 个答案:

答案 0 :(得分:4)

没有理由在列表上进行2次传递。

假设:谓词是简单的布尔值,如果必须进行评估,那么显然成本可能会改变。

如果((condtionA || conditionB)== true)则If-for和For-If都是1遍。如果两个谓词都可以为真,那么显然你只想进行一次传递。

doA和doB并不重要,因为我们假设它们在If-for和For-If中都是相同的。

如果谓词可以在评估过程中发生变化,则必须考虑这一点。

你问的是一般问题,所以答案是一般性的,含糊不清,没有更多细节。

好了,现在你已经提供了额外的信息(列表只有5个元素长,这是构建过程的一部分,谓词是静态布尔值)我们可以看到这里的瓶颈是doA / B函数。因此,您应该只进行一次循环。静态布尔检查可以忽略不计。

答案 1 :(得分:2)

使用您所谓的“If-For”方式而非“For-If”方式(可能是更为通用的版本)称为loop unswitching的优化。它是否真的是一个好主意取决于几个因素,例如(但不限于)

  • 是否允许转换(即条件没有副作用,doAdoB可以重新排序)
  • 你正在优化的内容(例如速度,可读性或w / e)虽然在这种情况下并没有真正有所作为
  • 数组是否适合缓存(迭代两次可能会使缓存未命中数增加一倍)
  • (JIT)编译器对它做什么完全,例如条件是否实际编译到分支,或者编译器是否为你做了循环开关
  • 处理器微体系结构(一些μarchs不喜欢内部循环中的分支比其他分支更多,即使这些分支是高度可预测的)

答案 2 :(得分:1)

它取决于您的代码试图解决的业务问题的性质。如果conditionA和conditionB都是简单的布尔变量而不是表达式,那么For-If会更好,因为您只在对象列表中循环一次。

我们基本上比较哪个表现更好:多次枚举对象列表或多次评估布尔表达式。如果你的conditionA / conditionB是非常复杂的布尔表达式,那么你的If-For将是一个更好的方法。

答案 3 :(得分:1)

让我们考虑我们在for循环和内部都进行相同数量的操作。如果这个标准我将采用第一种方法,在执行for循环之前使用if语句只是为了避免for循环中的迭代次数

此外,当您使用advance for循环时,与正常循环相比,执行相同操作需要更多时间。

如果我错了,请纠正我。

答案 4 :(得分:1)

这取决于!如果存在条件A和条件B都不为真的实际情况,则ifForMethod()解决方案是最佳的。如果存在conditionA和conditionB为真的情况,则解forIfMethod()是最好的,但在进入循环之前应该预先计算条件。

但您可以修改forIfMethod()以使其适合所有情况:

public void forIfMethod() {
  boolean a = conditionA;
  boolean b = conditionB;
  if (a || b) {
    for (Object o : listOfObjects) {
      if (a) {
        doA(o);
      }
      if (b) {
        doB(o);
      }
    }
  }
}

答案 5 :(得分:1)

首先,让我们来看看你到目前为止所展示的方法的复杂性:

  • ifForMethod执行 k 检查, m 返回true。对于每个 m ,都会对 n 对象进行迭代。那么,复杂性是 k + nm
  • forIfMethod迭代 n 对象,并在每次迭代时执行 k 比较。那么,复杂性是 k + n(k-1)= nk

在这两种情况下,所有 k 条件必须至少评估一次,因此这里的区别实际上是 nm n(k-1) )加数。渐近地, m 只是 k 的一小部分(你说 m 大约是 .75k ),所以这些都是O( nk ),但是 k + nm< k + n(k-1),因此ifForMethod可能比forIfMethod更快。实际运行时间的差异取决于诸如迭代数组所花费的实际时间以及 k 的大小等因素。您将开始涉及内存局部性问题(包括对象和代码)。

不过,这是一种你可能会感兴趣的方法。理想情况下,您只需要遍历对象列表一次,并且您不希望多次检查布尔条件。您可以抽象出您正在执行的操作,以便将它们组合成单个操作(并且您只需合并那些与真实条件相对应的操作),然后执行该复合操作列表中的每个元素。这是执行此操作的一些代码。

这个想法是有动作,你可以构建一个执行doA的动作和一个执行doB的动作。根据条件,您可以创建一个复合操作,如果doA条件为true,则包含doA操作;如果doB条件为true,则可以创建doB操作。然后迭代遍历对象,并调用对每个对象执行复合操作。渐近地,这是一个 k + nm 方法,所以理论上它表现很好,但同样,这里的实际性能将取决于一些棘手的常量和内存局部性问题。

import java.util.ArrayList;
import java.util.List;

public class CompoundActionExample {

    /**
     * An action is used to do something to an argument.
     */
    interface Action {
        void act( Object argument );
    }

    /**
     * A compound action is an action that acts on an argument
     * by passing the argument to some other actions.
     */
    static class CompoundAction implements Action {
        /**
         * The list of actions that the compound action will perform.  Additional
         * actions can be added using {@link #add(Action)}, and this list is only
         * accessed through the {@link #act(Object)} method.
         */
        private final List<CompoundActionExample.Action> actions;

        /**
         * Create a compound action with the specified list of actions.
         */
        CompoundAction( final List<CompoundActionExample.Action> actions ) {
            this.actions = actions;
        }

        /**
         * Create a compound action with a fresh list of actions.
         */
        CompoundAction() { 
            this( new ArrayList<CompoundActionExample.Action>() );
        }

        /**
         * Add an action to the compound action.
         */
        public void add( CompoundActionExample.Action action ) {
            actions.add( action );
        }

        /**
         * Act on an argument by passing the argument to each of the 
         * compound action's actions.
         */
        public void act( final Object argument) {
            for ( CompoundActionExample.Action action : actions ) {
                action.act( argument );
            }
        }
    }

    public static void main(String[] args) {
        // Some conditions and a list of objects
        final boolean conditionA = true;
        final boolean conditionB = false;
        final Object[] listOfObjects = { "object1", "object2", "object3" };

        // A compound action that encapsulates all the things you want to do
        final CompoundAction compoundAction = new CompoundAction();

        // If conditionA is true, add an action to the compound action that 
        // will perform doA.  conditionA is evaluated exactly once.
        if ( conditionA ) {
            compoundAction.add( new Action() {
                public void act( final Object argument) {
                    System.out.println( "doA("+argument+")" ); // doA( argument );
                }
            });
        }

        // If conditionB is true, add an action to the compound action that
        // will perform doB. conditionB is evaluted exactly once.
        if ( conditionB )  {
            compoundAction.add( new Action() {
                public void act(Object argument) {
                    System.out.println( "doB("+argument+")" ); // doB( argument );
                }
            });
        }

        // For each object, apply the compound action
        for ( final Object o : listOfObjects ) {
            compoundAction.act( o );
        }
    }
}

答案 6 :(得分:0)

第一个(if-for)对我来说听起来不错..因为对于第一种情况,将会有一次检查整个for循环。但在第二种情况下,将检查每个循环。

答案 7 :(得分:0)

我认为这取决于更多的事情,例如:如果你发现conditionA,循环会破坏吗? conditionA和conditionB可以共存吗?我可以使用if-else吗?

只是在寻找你所呈现的内容,我认为第二种方法更好。您只需循环一次并在同一循环中检查两次。在我看来,它也更具可读性。

答案 8 :(得分:0)

第二个就你做了多少比较而言更有效率。

检查条件a,1计算。 如果为true,则为Object.size计算器。

检查条件b,1计算。 如果为true,则为Object.size计算。 最小值,2,Max Object.size * 2

对于方法2,您将始终执行Object.size * 2计算。

如果两项检查均为假,请考虑您的“最坏情况”。方式1只进行2次计算。方式2将执行Object.size * 2.它与您的函数无关,因为在这两种情况下它总是需要相同的时间。

即使在你的“最佳情况”中,如果两项检查都是真的,你仍然会对A进行N-1次检查,对B进行N-1次检查。

我认为最好的办法是用最少的计算来做。

public void ifForMethod() {
    if (conditionA) {
        if(conditionB){
            for (Object o : listOfObjects) {
                doA(o);
                doB(o);
            }
        else{
            for (Object o : listOfObjects) {
                doA(o);
            }    
        }
    }
    else if (conditionB) {
        for (Object o : listOfObjects) {
            doB(o);
        }
    }
}

您执行2次检查操作,然后仅在最大值

循环列表一次

答案 9 :(得分:-1)

有几件事:

  1. 这是不成熟的优化吗?如果一种方式比另一种方式更快,这是否真的重要,取决于数据,它可能不是人类明显的差异。
  2. 我会质疑软件的设计。为什么同一个对象有两个可能的条件?我建议将设计分解为多个对象。也许使用子类来执行不同的逻辑或使用策略模式。如果不更好地了解你在做什么,我就无法更具体。