等效静态和非静态方法的速度差别很大

在这段代码中,当我在main方法中创build一个对象,然后调用这个对象的方法: ff.twentyDivCount(i) (运行在16010毫秒)时,运行速度比使用这个注释调用要快得多: twentyDivCount(i) 59516毫秒)。 当然,当我运行它而不创build一个对象时,我使得这个方法是静态的,所以它可以在main中调用。

 public class ProblemFive { // Counts the number of numbers that the entry is evenly divisible by, as max is 20 int twentyDivCount(int a) { // Change to static int.... when using it directly int count = 0; for (int i = 1; i<21; i++) { if (a % i == 0) { count++; } } return count; } public static void main(String[] args) { long startT = System.currentTimeMillis();; int start = 500000000; int result = start; ProblemFive ff = new ProblemFive(); for (int i = start; i > 0; i--) { int temp = ff.twentyDivCount(i); // Faster way // twentyDivCount(i) - slower if (temp == 20) { result = i; System.out.println(result); } } System.out.println(result); long end = System.currentTimeMillis();; System.out.println((end - startT) + " ms"); } } 

编辑:到目前为止,似乎不同的机器产生不同的结果,但使用JRE 1.8。*是原来的结果似乎一贯转载的地方。

使用JRE 1.8.0_45我得到了类似的结果。

调查:

  1. 使用-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining运行java -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining VM选项显示两个方法都被编译和内联
  2. 查看生成的方法本身的程序集显示没有显着差异
  3. 然而,一旦它们被内联, main生成中的生成assembly是非常不同的,实例方法被更加积极地优化,特别是在循环展开方面

然后我再次运行你的testing,但用不同的循环展开设置来确认上面的怀疑。 我运行你的代码:

  • -XX:LoopUnrollLimit=0 ,两种方法运行缓慢(类似于默认选项的静态方法)。
  • -XX:LoopUnrollLimit=100 ,两种方法运行速度都很快(类似于使用默认选项的实例方法)。

作为一个结论似乎是,使用默认设置, 热点1.8.0_45的JIT不能展开循环,当方法是静态的(虽然我不知道为什么它的行为方式)。 其他JVM可能会产生不同的结果。

根据一个assylias的答案只是一个未经证实的猜测。

JVM使用一个循环展开的阈值,类似于70.不pipe出于何种原因,静态调用稍微大一点,并且不会展开。

更新结果

  • 在52以下的LoopUnrollLimit中,两个版本都很慢。
  • 在52到71之间,只有静态版本很慢。
  • 71以上,两个版本都很快。

这很奇怪,因为我的猜测是静态调用在内部表示中略微大一些,并且OP遇到了一个奇怪的情况。 但差异似乎是20左右,这是没有意义的。

 -XX:LoopUnrollLimit=51 5400 ms NON_STATIC 5310 ms STATIC -XX:LoopUnrollLimit=52 1456 ms NON_STATIC 5305 ms STATIC -XX:LoopUnrollLimit=71 1459 ms NON_STATIC 5309 ms STATIC -XX:LoopUnrollLimit=72 1457 ms NON_STATIC 1488 ms STATIC 

对于那些愿意尝试, 我的版本可能是有用的。

在debugging模式下执行此操作时,实例和静态情况下的数字相同。 这进一步意味着JIT在静态情况下将代码编译为本地代码犹如在实例方法中一样。

为什么这样做? 这很难说; 如果这是一个更大的应用程序,它可能会做正确的事情…

我只是略微调整了testing,我得到了以下结果:

输出:

 Dynamic Test: 465585120 232792560 232792560 51350 ms Static Test: 465585120 232792560 232792560 52062 ms 

注意

当我分别testing他们时,dynamic时间约为52秒,静态时间约为200秒。

这是该计划:

 public class ProblemFive { // Counts the number of numbers that the entry is evenly divisible by, as max is 20 int twentyDivCount(int a) { // Change to static int.... when using it directly int count = 0; for (int i = 1; i<21; i++) { if (a % i == 0) { count++; } } return count; } static int twentyDivCount2(int a) { int count = 0; for (int i = 1; i<21; i++) { if (a % i == 0) { count++; } } return count; } public static void main(String[] args) { System.out.println("Dynamic Test: " ); dynamicTest(); System.out.println("Static Test: " ); staticTest(); } private static void staticTest() { long startT = System.currentTimeMillis();; int start = 500000000; int result = start; for (int i = start; i > 0; i--) { int temp = twentyDivCount2(i); if (temp == 20) { result = i; System.out.println(result); } } System.out.println(result); long end = System.currentTimeMillis();; System.out.println((end - startT) + " ms"); } private static void dynamicTest() { long startT = System.currentTimeMillis();; int start = 500000000; int result = start; ProblemFive ff = new ProblemFive(); for (int i = start; i > 0; i--) { int temp = ff.twentyDivCount(i); // Faster way if (temp == 20) { result = i; System.out.println(result); } } System.out.println(result); long end = System.currentTimeMillis();; System.out.println((end - startT) + " ms"); } } 

我也改变了testing的顺序:

 public static void main(String[] args) { System.out.println("Static Test: " ); staticTest(); System.out.println("Dynamic Test: " ); dynamicTest(); } 

我得到这个:

 Static Test: 465585120 232792560 232792560 188945 ms Dynamic Test: 465585120 232792560 232792560 50106 ms 

正如你所看到的,如果在静态之前调用dynamic,那么静态速度显着降低。

基于这个基准:

推测这一切都取决于JVM的优化。 因此我build议您按照经验法则来使用静态和dynamic方法。

规则:

Java:何时使用静态方法

请尝试:

 public class ProblemFive { public static ProblemFive PROBLEM_FIVE = new ProblemFive(); public static void main(String[] args) { long startT = System.currentTimeMillis(); int start = 500000000; int result = start; for (int i = start; i > 0; i--) { int temp = PROBLEM_FIVE.twentyDivCount(i); // faster way // twentyDivCount(i) - slower if (temp == 20) { result = i; System.out.println(result); System.out.println((System.currentTimeMillis() - startT) + " ms"); } } System.out.println(result); long end = System.currentTimeMillis(); System.out.println((end - startT) + " ms"); } int twentyDivCount(int a) { // change to static int.... when using it directly int count = 0; for (int i = 1; i < 21; i++) { if (a % i == 0) { count++; } } return count; } }