Java效率

Java效率,java,performance,Java,Performance,我在玩一段代码,计算计算一些Java代码所需的时间,以了解Java某些功能的效率或低效。这样做,我现在陷入了一些非常奇怪的影响,我只是无法解释自己。也许你们中的某个人能帮我理解 public class PerformanceCheck { public static void main(String[] args) { List<PerformanceCheck> removeList = new LinkedList<PerformanceCheck>()

我在玩一段代码,计算计算一些Java代码所需的时间,以了解Java某些功能的效率或低效。这样做,我现在陷入了一些非常奇怪的影响,我只是无法解释自己。也许你们中的某个人能帮我理解

public class PerformanceCheck {

 public static void main(String[] args) {
    List<PerformanceCheck> removeList = new LinkedList<PerformanceCheck>();

    int maxTimes = 1000000000;

    for (int i=0;i<10;i++) {
        long time = System.currentTimeMillis();

        for (int times=0;times<maxTimes;times++) {
            // PERFORMANCE CHECK BLOCK START

            if (removeList.size() > 0) {
                testFunc(3);
            }

            // PERFORMANCE CHECK BLOCK END
        }

        long timeNow = System.currentTimeMillis();
        System.out.println("time: " + (timeNow - time));
    }
 }

 private static boolean testFunc(int test) {
    return 5 > test;
 }

}
而将removeList.size()>0和testFunc(3)的任何组合替换为任何其他组合都会获得更好的结果。例如:

...
if (removeList.size() == 0) {
    testFunc(3);
}
...
结果为(每次调用testFunc):

即使调用两个相互独立的函数,也会减少计算时间:

...
if (removeList.size() == 0);
    testFunc(3);
...
结果:

time: 6
time: 5
time: 0
time: 0
...
在我的初始示例中,只有这个特定的组合需要很长时间。这让我很恼火,我真的很想理解。它有什么特别之处

谢谢

补充:

在第一个示例中更改testFunc()

if (removeList.size() > 0) {
                testFunc(times);
}
去别的地方,比如

private static int testFunc2(int test) {
    return 5*test;
}

这些基准测试非常困难,因为编译器非常聪明。一个猜测是:由于testFunc()的结果被忽略,编译器可能正在完全优化它。添加一个计数器,类似于

   if (testFunc(3))
     counter++;

为了彻底起见,在最后做一个
System.out.println(counter)

这真是令人惊讶。生成的字节码除了条件码之外是相同的,条件码是
ifle
vs
ifne

如果您使用
-Xint
关闭JIT,结果会更加合理。第二个版本慢2倍。这与JIT优化有关


我假设它可以优化第二种情况下的检查,但不能优化第一种情况下的检查(无论出于何种原因)。尽管这意味着它完成了函数的工作,但缺少条件会使事情变得更快。它避免了管道暂停等问题。

好吧,我很高兴不用处理Java性能优化问题。我自己用Java JDK 7 64位进行了尝试。结果是任意的;)。在进入循环之前,我正在使用哪个列表,或者是否缓存size()的结果,都没有区别。此外,完全删除测试函数几乎没有什么区别(因此它也不能成为分支预测命中)。 优化标志可以提高性能,但也是任意的

这里唯一合乎逻辑的结果是,JIT编译器有时能够优化语句(这并不难实现),但它似乎相当随意。我喜欢C++语言的一个原因,其中行为至少是确定性的,即使有时是任意的。
顺便说一句,在最新的Eclipse中,像在Windows上一样,通过IDE“Run”(无调试)运行此代码比从控制台运行此代码慢10倍,所以…

当运行时编译器可以计算出
testFunc
的计算结果为常数时,我相信它不会计算循环,这就解释了加速

当条件为
removeList.size()==0
时,函数
testFunc(3)
将计算为一个常数。当条件为
removeList.size()!=0
内部代码从未计算过,因此无法加速。您可以按如下方式修改代码:

for (int times = 0; times < maxTimes; times++) {
            testFunc();  // Removing this call makes the code slow again!
            if (removeList.size() != 0) {
                testFunc();
            }
        }

private static boolean testFunc() {
    return testFunc(3);
}
编译器可能会尝试(在执行之前)进行预优化,但显然不会在参数作为整数传入并在条件表达式中求值的情况下进行预优化

您的基准测试返回的时间如下

time: 107
time: 106
time: 0
time: 0
...

建议运行时编译器需要2次外部循环迭代才能完成优化。使用
-server
标志编译可能会返回基准中的所有0。

虽然与此问题没有直接关系,但这是如何使用游标卡尺正确地对代码进行微基准测试。下面是您的代码的修改版本,以便它可以与卡钳一起运行。必须对内部循环进行一些修改,以便VM不会对其进行优化。它非常聪明地意识到什么都没有发生

在对Java代码进行基准测试时,也有很多细微差别。我写了一些我在会议上遇到的问题,比如过去的历史如何影响当前的结果。通过使用卡钳,您将避免许多此类问题


  • 每次迭代的时间都非常快。这意味着JIT已经检测到您的代码没有做任何事情,并且已经消除了它。细微的更改可能会混淆JIT,它无法确定代码是否做了任何事情,这需要一些时间


    如果您将测试更改为做一些稍微有用的事情,那么差异将消失。

    并且您多次以不同的顺序运行此测试,对吗?顺序不重要,只是为了确保。另外,你是否得到了与下面建议的nanoTime相同的结果?这种微型基准测试是一个非常糟糕的主意。还要认识到,如果JIT意识到实际上什么也没有发生,它可以在任意时间做出各种决策,并完全优化代码。它更精确。更多讨论是的,我运行了多次,结果与nanoTime相同。对于第一个代码:time:2256414917 time:2256004749。谢谢你在nanoTime上的提示。尽管如此,第一个代码需要2秒以上的时间,而其他示例甚至不需要10毫秒。所有这些关于纳米时间()或毫秒时间()的讨论就像是在争论是否应该使用创可贴或布来帮助被反坦克炮弹击中的人。但这将使第一个版本更快,但事实并非如此。可能是@prelic建议的顺序。出于某种原因,JIT第一次没有优化调用,但第二次就解决了。我确实在本地以两种方式运行了它,并且没有改变任何事情。我尝试了包括计数器并将testFunc(3)更改为testFunc(次)。第一个版本(不调用testFunc())仍然慢2倍。问题是,它也依赖于testFunc()。交换工商业污水附加费
    for (int times = 0; times < maxTimes; times++) {
                testFunc();  // Removing this call makes the code slow again!
                if (removeList.size() != 0) {
                    testFunc();
                }
            }
    
    private static boolean testFunc() {
        return testFunc(3);
    }
    
    private static int testFunc2(int test) {
        return 5*test;
    }
    
    time: 107
    time: 106
    time: 0
    time: 0
    ...
    
    public class PerformanceCheck extends SimpleBenchmark {
    
    public int timeFirstCase(int reps) {
        List<PerformanceCheck> removeList = new LinkedList<PerformanceCheck>();
        removeList.add( new PerformanceCheck());
        int ret = 0;
    
        for( int i = 0; i < reps; i++ )  {
            if (removeList.size() > 0) {
                if( testFunc(i) )
                    ret++;
            }
        }
    
        return ret;
    }
    
    public int timeSecondCase(int reps) {
        List<PerformanceCheck> removeList = new LinkedList<PerformanceCheck>();
        removeList.add( new PerformanceCheck());
        int ret = 0;
    
        for( int i = 0; i < reps; i++ )  {
            if (removeList.size() == 0) {
                if( testFunc(i) )
                    ret++;
            }
        }
    
        return ret;
    }
    
    private static boolean testFunc(int test) {
        return 5 > test;
    }
    
    public static void main(String[] args) {
        Runner.main(PerformanceCheck.class, args);
    }
    }
    
     0% Scenario{vm=java, trial=0, benchmark=FirstCase} 0.60 ns; σ=0.00 ns @ 3 trials
    50% Scenario{vm=java, trial=0, benchmark=SecondCase} 1.92 ns; σ=0.22 ns @ 10 trials
    
     benchmark    ns linear runtime
     FirstCase 0.598 =========
    SecondCase 1.925 ==============================
    
    vm: java
    trial: 0