等效静态和非静态方法的速度差别很大
在这段代码中,当我在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我得到了类似的结果。
调查:
- 使用
-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining
运行java-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining
VM选项显示两个方法都被编译和内联 - 查看生成的方法本身的程序集显示没有显着差异
- 然而,一旦它们被内联,
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; } }