对java if-else性能感到困惑
我正在对一个方法的性能进行调查,最后确定开销是由if-else语句的“else”部分引起的。我编写了一个小程序来说明性能差异,即使代码的else部分从未执行:对java if-else性能感到困惑,java,performance,Java,Performance,我正在对一个方法的性能进行调查,最后确定开销是由if-else语句的“else”部分引起的。我编写了一个小程序来说明性能差异,即使代码的else部分从未执行: public class TestIfPerf { public static void main( String[] args ) { boolean condition = true; long time = 0L; int value = 0;
public class TestIfPerf
{
public static void main( String[] args )
{
boolean condition = true;
long time = 0L;
int value = 0;
// warm up test
for( int count=0; count<10000000; count++ )
{
if ( condition )
{
value = 1 + 2;
}
else
{
value = 1 + 3;
}
}
// benchmark if condition only
time = System.nanoTime();
for( int count=0; count<10000000; count++ )
{
if ( condition )
{
value = 1 + 2;
}
}
time = System.nanoTime() - time;
System.out.println( "1) performance " + time );
time = System.nanoTime();
// benchmark if else condition
for( int count=0; count<10000000; count++ )
{
if ( condition )
{
value = 1 + 2;
}
else
{
value = 1 + 3;
}
}
time = System.nanoTime() - time;
System.out.println( "2) performance " + time );
}
}
公共类TestIfPerf
{
公共静态void main(字符串[]args)
{
布尔条件=真;
长时间=0L;
int值=0;
//预热试验
对于(int count=0;count有两种可能的解释:
- 你得到的时间被基准缺陷扭曲了。你做了很多错事——参见
- 带有
else
的版本在每次循环迭代中确实花费了稍长的时间。如果是这种情况,有许多可能的解释。处理它的最佳方法是查看JIT编译器生成的本机代码并分析其性能
但归根结底,这并不令人惊讶(见上文),对于绝大多数Java应用程序来说也没有任何实际后果,而是由应用程序决定是否需要“if-then”或“if-then-else”
而且,您可能从像这样的人工微基准测试中学到的任何东西对实际代码是否有指导意义也值得怀疑。JIT编译器可能会在比您的测试更复杂的级别上优化代码。您可能会在这里看到什么(如果您的基准测试没有缺陷)不太可能在实际应用程序中反映出来。如果在第二次度量中注释掉else分支,则会变得更加奇怪:它仍然较慢,尽管它现在是相同的代码。好消息是,如果您使用单独的方法提取此代码,它在第二次度量中运行得更快
我能想到的唯一一件事是JVM只优化了长方法的第一部分。是的:如果我把if-else度量放在第一位,它会更快。你的测试很糟糕。如果我交换测试条件,我会得到完全相反的结果:
1) performance 5891113
2) performance 15216601
2) performance 5428062
1) performance 15087676
这可能与JVM在执行过程中优化代码有关。如果复制/粘贴几次条件,我会得到以下结果:
2) performance 6258694
1) performance 34484277
2) performance 978
1) performance 978
2) performance 908
必须与VM init(预热时间很短)或时间测量中的抖动(与VM启动相关)相关
如果交换循环,则循环2会更快:-)
一般来说,热点JIT是不错的,但不可靠,也不是那么确定。
在java中获得最佳性能
- 避免对象创建
- 尽可能将变量标记为final。有时这并没有什么区别,但有时确实如此。在循环中访问实例变量时,将它们复制到final本地
- 如果可能的话,将方法标记为final
- 如果你想让一个方法内联,保持简短,也许可以将其分解。我通过测试多次验证了这一点,它是通过拆分“void foo(){if(..)return..stuff…}”来内联的/拆分速度更快,就像拆分“foo(){if(..)return;else foo1()}void foo1(){stuff}”
一般来说,使用微基准测试来证明性能模式是非常困难的,因为您不知道到底是什么触发了内联、jit编译和进一步的运行时优化。jit中存在阈值,因此可能会发生性能降低的情况,因为您向方法添加了语句或添加了existi的子类ng类。Java代码:
public class Main {
public static void main(String[] args) {
boolean cond = true;
int nothing = 0;
for (int i = 0; i < 20; i++) {
int value = 0;
long time = System.nanoTime();
for (int count = 0; count < 10000000; count++) {
if (cond) {
value = 1 + 2;
}
}
time = System.nanoTime() - time;
System.out.println("1) performance: " + time);
nothing = value; // prevent java ignoring value
value = 0;
time = System.nanoTime();
for (int count = 0; count < 10000000; count++) {
if (cond) {
value = 1 + 2;
} else {
value = 1 + 3;
}
}
time = System.nanoTime() - time;
System.out.println("2) performance: " + time);
nothing = value; // prevent java ignoring value
}
nothing = nothing + 1;
}
}
for (int count = 0; count < 10000000; count++) {
// start of the if
if (cond) {
value = 1 + 2;
}
// end of the if
}
for (int count = 0; count < 10000000; count++) {
// start of the if
if (cond) {
value = 1 + 2;
} else {
value = 1 + 3;
}
// end of the if
}
Java代码:
for (int count = 0; count < 10000000; count++) {
// start of the if
if (cond) {
value = 1 + 2;
}
// end of the if
}
for (int count = 0; count < 10000000; count++) {
// start of the if
if (cond) {
value = 1 + 2;
} else {
value = 1 + 3;
}
// end of the if
}
根据您的基准测试,else
每次迭代的成本为0.14ns(在Linux上)。这并没有让我觉得不合理。-)开玩笑的是,我强烈建议您阅读Brian发布的链接。因此,在一个有问题的基准测试的1000万次迭代中,else子句会导致1或2毫秒的损失。在什么样的应用程序中,如此微小的差异是显著的?您确定不应该在中调整您的DB查询吗stead?您是否尝试过反转测试条件?如果是第一个,如果是第二个。这样做,看看会发生什么。final
变量和方法对运行时性能没有影响JVM根据运行时场景决定内联什么,提升到寄存器,并使用vtable。请参阅感谢大家提供的宝贵信息。W我将进一步研究微基准标记。不幸的是,由于我所做的工作的性质,我们有时需要进入这一领域。@Kuo。这不是真的。最终变量值不能修改,因此可以保存在寄存器中。最终方法不能重写,所以这个。finalMethod()可以内联而无需任何进一步检查。如果虚拟机使用“final”提示,则取决于虚拟机以及随时间的变化。更正。“.final vars不能由其他线程修改…”如果您添加一些解释,我可能会投票支持此答案。提及JIT
63: lstore_3 //store a long value in a local variable 3
64: iconst_0 //load the int value 0 onto the stack
65: istore 5 //store int value into variable #index
67: goto 84 //goes to another instruction at branchoffset
70: iload_1 //load an int value from local variable 1
71: ifeq 79 //if value is 0, branch to instruction at branchoffset
74: iconst_3 //load the int value 3 onto the stack
75: istore_2 //store int value into variable 2
76: goto 81 //goes to another instruction at branchoffset
79: iconst_4 //load the int value 4 onto the stack
80: istore_2 //store int value into variable 2
81: iinc 5, 1 //increment local variable #index by signed byte const
84: iload 5 //load an int value from a local variable #index
86: ldc #22; //push a constant #index from a constant pool (String, int or float) onto the stack - int 10000000
88: if_icmplt 70 //if value1 is less than value2, branch to instruction at branchoffset