这是Java循环的一个有用的优化吗?

5
考虑以下内容:
1.
    for (final Bar a : bars) {
        for (final Foo f : foos) {
            doSomethingWith(f.foo(), a.bar());
        }
    }

而且:
2.
    for (final Bar a : bars) {
        final Object bar = a.bar();
        for (final Foo f : foos) {
            doSomethingWith(f.foo(), bar);
        }
    }

这种优化真的有用吗?还是编译器会自动进行优化?

如果bar()是一个getter(例如getBar()),你的答案会改变吗?

如果我要针对Android开发,你的答案会改变吗?


2
工作任务:为学生编译两个版本并比较字节码输出。这里提供参考链接:https://docs.oracle.com/javase/7/docs/technotes/tools/windows/javap.html - DwB
1
编译器能否知道对于给定的 aa.bar() 总是返回相同的值? - Thomas Padron-McCarthy
@ThomasPadron-McCarthy 当然不行。 - Evgeniy Mishustin
@BobMalooga 不是的,请注意bar是在foos循环内调用的。 - Ofek Ron
你有一个完美的机会来测试。添加一些时间变量并开始测量,如果你不想开始计算 O-操作的话。 - heniv181
显示剩余2条评论
3个回答

1
我已经按照你的问题尝试了两个例子。基于此,我必须说第二种方法会更好。(尽管我没有考虑多线程

Test.java

public class Test{

    public static void main(String... args){

        String[][] arr2 = new String[5][5]; 
        for (final String[] obj : arr2)
        {
            for (final String str : obj)
            System.out.println(str.length() +" " + obj.length); 
        }
    }
}

编译后再次反编译后,我得到了这个结果。

 * Decompiled with CFR 0_114.
 */
import java.io.PrintStream;

public class Test {
    public static /* varargs */ void main(String ... arrstring) {
        String[][] arrstring2;
        String[][] arrstring3 = arrstring2 = new String[5][5];
        int n = arrstring3.length;
        for (int i = 0; i < n; ++i) {
            String[] arrstring4;
            for (String string : arrstring4 = arrstring3[i]) { //assignment will take place m*n.
                System.out.println("" + string.length() + " " + arrstring4.length);
             //this arrstring4.length will execute m*n (in this case).So, this will less efficient than others.
            }
        }
    }
}

Test1.java

public class Test1{

    public static void main(String... args){

        String[][] arr2 = new String[5][5]; 
        for (final String[] obj : arr2)
        {
            int value = obj.length;
            for (final String str : obj)
                System.out.println(str.length() +" " + value); 
        }
    }
}

编译后再次反编译,我得到了这个结果。
/*
 * Decompiled with CFR 0_114.
 */
import java.io.PrintStream;

public class Test1 {
    public static /* varargs */ void main(String ... arrstring) {
        String[][] arrstring2;
        for (String[] arrstring3 : arrstring2 = new String[5][5]) {
            int n = arrstring3.length;  //Assignment will take place M times only.
            //this will calculate M times only. So, it will definitely faster than above. 
            for (String string : arrstring3) { 
                System.out.println("" + string.length() + " " + n);
               //here n is calculate M times but can be printed M*N times.
            }
        }
    }
}

0

尽管看起来很惊人,但请尝试:

for (final Bar a : bars) {
    innerLoop(a, foos);
}

 private final innerLoop(Bar a, Collection<Foo> foos) {
    for (final Foo f : foos) {
        doSomethingWith(f.foo(), a.bar());
 }

只需要进行一点小调整,试试看


你希望通过这样做获得什么?顺便说一下,你还有一个语法错误。 - Hulk

-1
在看到评论并回答它们后,我意识到即使编译器深入理解 a.bar() 的代码,也无法保证在多线程环境(如 Java)中 a.bar() 的结果不会改变。此外,与反射相结合,如果 bar 是成员字段,则可以通过反射更改它,编译器事先不知道这一点,这可能导致简单的 return bar; 不可预测。
因此,目前对我来说,在我的代码中继续使用该优化似乎是合理的。

“保证在多线程环境下,a.bar() 的结果不会改变” - 除非该方法包含某些同步或友元(如 volatile),否则编译器可以忽略其他线程。没有同步,JMM 不保证其他线程所做的更改可见性,硬件也不保证任何东西(考虑每个核心缓存)。+++ 话虽如此,在 Android 上可能是一个有用的优化,因为它不像服务器类 JVM 那样聪明。 - maaartinus
请注意,在foos对象的迭代之间可能会对成员进行更改,volatile在这种情况下无法帮助,只有在正确使用同步时才能发挥作用。我觉得很难相信编译器可以分析所有情况下同步的正确性... - Ofek Ron
如果 a.bar() 是一个返回 final 字段的 final 方法呢? - shmosel
@shmosel 这里方法是否为final不重要。读取final字段的值保证是可见的。通过反射更改final字段的值是可能的,但您将失去这个保证。 +++ 我的观点是编译器可能会内联该方法,并且如果它足够简单并且不包含同步或友元,则可以避免冗余加载。 - maaartinus
编译器肯定不关心你是否正确同步。首先,它会查看是否可以内联方法,如果不能,则完成操作。否则,它会寻找同步的方式。如果有任何同步方式,则必须提供可见性保证。否则,它可以避免冗余负载。 - maaartinus
显示剩余2条评论

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