等效静态和非静态方法的速度差异很大
Posted
技术标签:
【中文标题】等效静态和非静态方法的速度差异很大【英文标题】:Large difference in speed of equivalent static and non static methods 【发布时间】:2015-08-07 21:53:38 【问题描述】:在这段代码中,当我在 main
方法中创建一个对象然后调用该对象方法:ff.twentyDivCount(i)
(runs in 16010 ms) 时,它的运行速度比使用此注释调用它快得多:twentyDivCount(i)
(runs 59516 毫秒)。当然,当我在不创建对象的情况下运行它时,我将方法设为静态,因此可以在 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.* 似乎可以始终如一地重现原始结果。
【问题讨论】:
你是如何运行你的基准测试的?我敢打赌,这是 JVM 没有足够时间优化代码的产物。 看来 JVM 已经有足够的时间来编译并为 main 方法执行 OSR,如+PrintCompilation +PrintInlining
所示
我已经尝试过代码 sn-p ,但我没有像 Stabbz 所说的那样得到任何时差。他们56282ms(使用实例)54551ms(作为静态方法)。
@PatrickCollins 五秒钟就足够了。我rewrote it a bit 以便您可以测量两者(每个变体启动一个 JVM)。我知道作为基准它仍然存在缺陷,但它足以令人信服:1457 ms STATIC vs 5312 ms NON_STATIC。
还没有详细调查这个问题,但是这个可能是相关的:shipilev.net/blog/2015/black-magic-method-dispatch(也许 Aleksey Shipilëv 可以在这里启发我们)
【参考方案1】:
使用 JRE 1.8.0_45 我得到了类似的结果。
调查:
-
使用
-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining
VM 选项运行 java 表明这两种方法都已编译和内联
查看生成的程序集的方法本身没有显着差异
然而,一旦它们被内联,main
中生成的程序集就大不相同了,实例方法得到了更积极的优化,尤其是在循环展开方面
然后我再次运行您的测试,但使用不同的循环展开设置来确认上述怀疑。我运行了你的代码:
-XX:LoopUnrollLimit=0
并且这两个方法运行缓慢(类似于带有默认选项的静态方法)。
-XX:LoopUnrollLimit=100
并且两种方法都运行得很快(类似于带有默认选项的实例方法)。
作为一个结论,似乎在默认设置下,热点 1.8.0_45 的 JIT 在方法为静态时无法展开循环(尽管我不确定为什么会这样行为方式)。其他 JVM 可能会产生不同的结果。
【讨论】:
在 52 和 71 之间,恢复了原始行为(至少在我的机器上,我的回答)。看起来静态版本要大 20 个单位,但为什么呢?这很奇怪。 @maaartinus 我什至不确定这个数字究竟代表什么 - 文档相当回避:“Unroll loop body with server compiler intermediate representation node count less than this value. 使用的限制服务器编译器是这个值的函数,而不是实际值。默认值随JVM运行的平台而异。"... 我也不知道,但我的第一个猜测是静态方法在任何单位中都会变得稍大一些,并且我们找到了重要的地方。然而,差异是相当大的,所以我目前的猜测是静态版本得到了一些优化,使其更大一些。我没有看过生成的asm。【参考方案2】:在调试模式下执行时,实例和静态情况的数字相同。这进一步意味着 JIT 在静态情况下会犹豫将代码编译为本机代码,就像在实例方法情况下一样。
为什么会这样?这很难说;如果这是一个更大的应用程序,它可能会做正确的事情......
【讨论】:
“为什么要这样做?很难说,如果这是一个更大的应用程序,它可能会做正确的事情。”或者你只是有一个奇怪的性能问题,它太大而无法实际调试。 (这并不难说。你可以看看 JVM 像 assylias 那样吐出的程序集。) @tmyklebu 或者我们有一个奇怪的性能问题,完全调试是不必要且昂贵的,并且有简单的解决方法。最后,我们在这里讨论的是 JIT,它的作者并不知道它在所有情况下的确切行为。 :) 看看其他答案,它们非常好并且非常接近地解释了这个问题,但到目前为止,仍然没有人知道为什么会发生这种情况。 @DraganBozanovic:当它在实际代码中引起真正的问题时,它不再是“不必要的完全调试”。【参考方案3】:我只是稍微调整了一下测试,得到了以下结果:
输出:
Dynamic Test:
465585120
232792560
232792560
51350 ms
Static Test:
465585120
232792560
232792560
52062 ms
注意
当我单独测试它们时,我得到了大约 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");
我还把测试的顺序改为:
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
如您所见,如果在静态之前调用动态,则静态的速度会大大降低。
基于此基准:
我假设这一切都取决于 JVM 优化。因此我 只是建议您按照经验法则使用静态和 动态方法。
经验法则:
Java: when to use static methods
【讨论】:
“你要遵循经验法则来使用静态和动态方法。”这个经验法则是什么?你引用的是谁/什么? @weston 抱歉,我没有添加我想要的链接:)。谢谢【参考方案4】:只是一个未经证实的猜测,基于 assylias 的回答。
JVM 使用一个阈值来展开循环,大约是 70。无论出于何种原因,静态调用稍大一些,并且不会展开。
更新结果
在下面的 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
对于那些愿意尝试的人,my version 可能会有用。
【讨论】:
是 '1456 ms' 时间吗?如果是,为什么你说静态很慢? @Tony 我混淆了NON_STATIC
和STATIC
,但我的结论是正确的。现已修复,谢谢。【参考方案5】:
请尝试:
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;
【讨论】:
20273 ms 到 23000+ ms,每次运行都不同以上是关于等效静态和非静态方法的速度差异很大的主要内容,如果未能解决你的问题,请参考以下文章