因此,我想对一些基本的java功能进行基准测试,以向这个问题添加一些信息:将方法声明为静态的收益是什么。
我知道编写基准测试有时并不容易,但这里发生的事情我无法解释。
请注意,我不感兴趣的是如何解决这个问题,而是为什么会发生这种情况*
测试类:
public class TestPerformanceOfStaticVsDynamicCalls {
private static final long RUNS = 1_000_000_000L;
public static void main( String [] args ){
new TestPerformanceOfStaticVsDynamicCalls().run();
}
private void run(){
long r=0;
long start, end;
for( int loop = 0; loop<10; loop++ ){
// Benchmark
start = System.currentTimeMillis();
for( long i = 0; i < RUNS; i++ ) {
r += addStatic( 1, i );
}
end = System.currentTimeMillis();
System.out.println( "Static: " + ( end - start ) + " ms" );
start = System.currentTimeMillis();
for( long i = 0; i < RUNS; i++ ) {
r += addDynamic( 1, i );
}
end = System.currentTimeMillis();
System.out.println( "Dynamic: " + ( end - start ) + " ms" );
// Do something with r to keep compiler happy
System.out.println( r );
}
}
private long addDynamic( long a, long b ){
return a+b;
}
private static long addStatic( long a, long b ){
return a+b;
}
}
我希望第一个循环是热身,接下来的循环会更快。
在Eclipse中运行这个程序会产生奇怪的结果:
Static: 621 ms
Dynamic: 631 ms
1000000001000000000
Static: 2257 ms
Dynamic: 2501 ms
2000000002000000000
Static: 2258 ms
Dynamic: 2469 ms
3000000003000000000
Static: 2231 ms
Dynamic: 2464 ms
4000000004000000000
所以wtf?它变慢了。为了交叉检查,我用java/c 7运行了相同的代码:
Static: 620 ms
Dynamic: 627 ms
1000000001000000000
Static: 897 ms
Dynamic: 617 ms
2000000002000000000
Static: 901 ms
Dynamic: 615 ms
3000000003000000000
Static: 888 ms
Dynamic: 616 ms
4000000004000000000
所以这里只有静态调用在下面的循环中变慢了。更奇怪的是,如果我在最后一次循环后重新排列代码,只打印< code>r,我会在Eclipse中看到:
Static: 620 ms
Dynamic: 635 ms
Static: 2285 ms
Dynamic: 893 ms
Static: 2258 ms
Dynamic: 900 ms
Static: 2280 ms
Dynamic: 905 ms
4000000004000000000
这与java/c 7:
Static: 620 ms
Dynamic: 623 ms
Static: 890 ms
Dynamic: 614 ms
Static: 890 ms
Dynamic: 616 ms
Static: 886 ms
Dynamic: 614 ms
4000000004000000000
同时改变eclipse中动态/静态基准的顺序:
Dynamic: 618 ms
Static: 626 ms
1000000001000000000
Dynamic: 632 ms
Static: 2524 ms
2000000002000000000
Dynamic: 617 ms
Static: 2528 ms
3000000003000000000
Dynamic: 622 ms
Static: 2506 ms
4000000004000000000
在java/c 7中:
Dynamic: 625 ms
Static: 646 ms
1000000001000000000
Dynamic: 2470 ms
Static: 633 ms
2000000002000000000
Dynamic: 2459 ms
Static: 635 ms
3000000003000000000
Dynamic: 2464 ms
Static: 645 ms
4000000004000000000
那么这里发生了什么呢?
编辑:一些系统信息:
Java version "1.7.0_55"
OpenJDK Runtime Environment (IcedTea 2.4.7) (7u55-2.4.7-1ubuntu1)
OpenJDK 64-Bit Server VM (build 24.51-b03, mixed mode)
Intel(R) Core(TM) i7-2720QM CPU @ 2.20GHz
第二版:
使用爪哇8:
Static: 620 ms
Dynamic: 624 ms
1000000001000000000
Static: 890 ms
Dynamic: 618 ms
2000000002000000000
Static: 891 ms
Dynamic: 616 ms
3000000003000000000
Static: 892 ms
Dynamic: 617 ms
4000000004000000000
其他代码排序在这里产生类似的奇怪(但其他)结果。
只是一个额外的注意事项。只有当我将long
用于r
和i
s时,我才能观察到这种奇怪的行为。如果我将它们转换为int
然后我得到这些计时:
Static: 352 ms
Dynamic: 353 ms
Static: 348 ms
Dynamic: 349 ms
Static: 349 ms
Dynamic: 348 ms
Static: 349 ms
Dynamic: 344 ms
因此,一个可能的结论是避免在这些情况下长时间
工作。至少在 Linux/Amd64 Java7 中,性能很重要。
看起来是Java向变量r
添加值的方式。我做了一些更改,添加了方法run2()
:
public class TestPerformanceOfStaticVsDynamicCalls {
private static final long RUNS = 1_000_000_000L;
public static void main(String[] args) {
System.out.println("Test run 1 =================================");
new TestPerformanceOfStaticVsDynamicCalls().run();
System.out.println("Test run 2 =================================");
new TestPerformanceOfStaticVsDynamicCalls().run2();
}
private void run2() {
long r = 0;
long start, end;
for (int loop = 0; loop < 10; loop++) {
// Benchmark
long stat = 0;
start = System.currentTimeMillis();
for (long i = 0; i < RUNS; i++) {
stat += addStatic(1, i);
}
end = System.currentTimeMillis();
System.out.println("Static: " + (end - start) + " ms");
long dyna = 0;
start = System.currentTimeMillis();
for (long i = 0; i < RUNS; i++) {
dyna += addDynamic(1, i);
}
end = System.currentTimeMillis();
System.out.println("Dynamic: " + (end - start) + " ms");
// If you really want to have values in "r" then...
r += stat + dyna;
// Do something with r to keep compiler happy
System.out.println(r);
}
}
private void run() {
long r = 0;
long start, end;
for (int loop = 0; loop < 10; loop++) {
// Benchmark
start = System.currentTimeMillis();
for (long i = 0; i < RUNS; i++) {
r += addStatic(1, i);
}
end = System.currentTimeMillis();
System.out.println("Static: " + (end - start) + " ms");
start = System.currentTimeMillis();
for (long i = 0; i < RUNS; i++) {
r += addDynamic(1, i);
}
end = System.currentTimeMillis();
System.out.println("Dynamic: " + (end - start) + " ms");
// If you really want to have values in "r" then...
// Do something with r to keep compiler happy
System.out.println(r);
}
}
private long addDynamic(long a, long b) {
return a + b;
}
private static long addStatic(long a, long b) {
return a + b;
}
}
结果是:
Test run 1 =================================
Static: 582 ms
Dynamic: 579 ms
1000000001000000000
Static: 2065 ms
Dynamic: 2352 ms
2000000002000000000
Static: 2084 ms
Dynamic: 2345 ms
3000000003000000000
Static: 2095 ms
Dynamic: 2347 ms
4000000004000000000
Static: 2102 ms
Dynamic: 2338 ms
5000000005000000000
Static: 2073 ms
Dynamic: 2345 ms
6000000006000000000
Static: 2074 ms
Dynamic: 2341 ms
7000000007000000000
Static: 2102 ms
Dynamic: 2355 ms
8000000008000000000
Static: 2062 ms
Dynamic: 2354 ms
9000000009000000000
Static: 2057 ms
Dynamic: 2350 ms
-8446744063709551616
Test run 2 =================================
Static: 584 ms
Dynamic: 582 ms
1000000001000000000
Static: 587 ms
Dynamic: 577 ms
2000000002000000000
Static: 577 ms
Dynamic: 579 ms
3000000003000000000
Static: 577 ms
Dynamic: 577 ms
4000000004000000000
Static: 578 ms
Dynamic: 579 ms
5000000005000000000
Static: 578 ms
Dynamic: 580 ms
6000000006000000000
Static: 577 ms
Dynamic: 579 ms
7000000007000000000
Static: 578 ms
Dynamic: 577 ms
8000000008000000000
Static: 580 ms
Dynamic: 578 ms
9000000009000000000
Static: 576 ms
Dynamic: 579 ms
-8446744063709551616
至于为什么直接加到r
,我一点头绪都没有。也许有人可以提供更多的见解,说明为什么在循环块
内访问r
会使事情变得慢得多。
序言:手动编写微基准几乎总是注定要失败。
有些框架已经解决了常见的基准测试问题。
>
JIT编译单元是一种方法。将多个基准测试合并到一个方法中会导致不可预测的结果。
JIT严重依赖于执行配置文件,即运行时统计数据。如果一个方法长时间运行第一个场景,JIT会为其优化生成的代码。当该方法突然切换到另一个场景时,不要期望它以相同的速度运行。
JIT可能会跳过优化未执行的代码。这会给这段代码留下一个不常见的陷阱。如果陷阱被击中,JVM将取消编译方法的优化,切换到解释器,然后使用新知识重新编译代码。例如,当您的方法run
在第一个热循环中首次编译时,JIT还不知道System.out.println
。一旦执行到达println
,早期编译的代码很可能会被取消优化。
方法越大——越难为即时编译器优化它。例如,似乎没有足够的备用寄存器来保存所有局部变量。这就是你的情况。
综上所述,您的基准测试似乎通过了以下场景:
优化循环:
0x0000000002d01054: add %rbx,%r14
0x0000000002d01057: add $0x1,%rbx ;*ladd
; - TestPerformanceOfStaticVsDynamicCalls::addDynamic@2
; - TestPerformanceOfStaticVsDynamicCalls::run@105
0x0000000002d0105b: add $0x1,%r14 ; OopMap{rbp=Oop off=127}
;*goto
; - TestPerformanceOfStaticVsDynamicCalls::run@116
0x0000000002d0105f: test %eax,-0x1c91065(%rip) # 0x0000000001070000
;*lload
; - TestPerformanceOfStaticVsDynamicCalls::run@92
; {poll}
0x0000000002d01065: cmp $0x3b9aca00,%rbx
0x0000000002d0106c: jl 0x0000000002d01054
循环使用额外的寄存器溢出:
0x0000000002d011d0: mov 0x28(%rsp),%r11 <---- the problem is here
0x0000000002d011d5: add %r10,%r11
0x0000000002d011d8: add $0x1,%r10
0x0000000002d011dc: add $0x1,%r11
0x0000000002d011e0: mov %r11,0x28(%rsp) ;*ladd
; - TestPerformanceOfStaticVsDynamicCalls::addStatic@2
; - TestPerformanceOfStaticVsDynamicCalls::run@33
0x0000000002d011e5: mov 0x28(%rsp),%r11 <---- the problem is here
0x0000000002d011ea: add $0x1,%r11 ; OopMap{[32]=Oop off=526}
;*goto
; - TestPerformanceOfStaticVsDynamicCalls::run@44
0x0000000002d011ee: test %eax,-0x1c911f4(%rip) # 0x0000000001070000
;*goto
; - TestPerformanceOfStaticVsDynamicCalls::run@44
; {poll}
0x0000000002d011f4: cmp $0x3b9aca00,%r10
0x0000000002d011fb: jl 0x0000000002d011d0 ;*ifge
; - TestPerformanceOfStaticVsDynamicCalls::run@25
P、 以下JVM选项对于分析JIT编译非常有用:
-XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -XX:+PrintAssembly -XX:CompileOnly=TestPerformanceOfStaticVsDynamicCalls
问题内容: 因此,我想对一些基本的Java功能进行基准测试,以为该问题添加一些信息:将方法声明为static有什么好处。 我知道编写基准有时并不容易,但是在这里发生的事情我无法解释。 请注意,我对如何解决此问题不感兴趣,但对为什么会发生* 测试类: 我期望第一个循环可以进行预热,而随后的循环可以更快。 在Eclipse中运行它会产生一些奇怪的结果: 那么wtf?它变慢了。为了进行交叉检查,我使用j
问题内容: JIT的循环展开策略是什么?或者,如果没有简单的答案,那么有什么方法可以检查循环中展开循环的位置/时间? 基本上,我上面有一段代码,具有静态的迭代次数(八),当我保持for循环不变时,它的效果很差。但是,当我手动展开循环时,效果会更好。我有兴趣了解JIT是否确实展开了循环,如果没有,那么为什么。 问题答案: 如果 JVM展开,则可以通过实际打印生成的程序集来最好地解决该循环。请注意,这
问题内容: 和别的: 两者都不起作用(强制关闭应用程序)。我可以尝试其他哪些选择? 问题答案: 您的代码失败,因为您在后台线程中执行睡眠,但是显示数据必须在UI线程中执行。 您必须从runOnUiThread(Runnable)运行displayData或定义处理程序并将消息发送给它。 例如:
问题内容: 在Solaris SPARC机器上对一些Java代码进行基准测试时,我注意到我第一次调用该基准测试函数时,它运行EXTREMELY的速度很慢(相差10倍): 第一| 1 | 25295.979毫秒 第二| 1 | 2256.990毫秒 第三 1 | 2250.575毫秒 为什么是这样?我怀疑是JIT编译器,有什么办法可以验证这一点? 编辑: 根据一些答案,我想澄清一下这段代码是我可以发
我在本地运行一个项目,并监视该想法的内存。exe保留 作业完成。Java语言exe已从列表中删除。内存并没有变小,intellij的速度很慢,在重启之前有点无响应 然后当我再次跑步时,它会再次发生 知道吗?
问题内容: 在大多数情况下,Java 8流允许比老式循环可读得多的代码。但是,根据我自己的经验和所阅读的内容,使用流而不是for循环可能会导致性能下降(或有时会有所改善),这有时很难预测。 在大型项目中,为每个循环编写基准测试似乎不可行,因此, 在决定是否用流替换循环时,关键因素是什么(例如,预期的集合大小,预期的百分比值被删除)。过滤,迭代操作的复杂性,归约或聚合的类型等) ,这些指标可能暗示可