我很好奇Java如何对具有互斥条件的多个“ if”语句进行优化,但是我不具备自己分析的知识。该问题基本上是该问题Performance difference of "if if" vs "if else if"
的Java版本我已经看到if
的{{1}}语句的答案,但是这个问题是针对具有互斥条件但不返回的return
语句的。
1。多个if语句
if
2。链式If-else语句
if (x == 0) doSomething();
if (x == 2) doSomething();
if (x == 5) doSomething();
问题
#1和#2是否执行相同的后编译?
(另外:如果是这样,那么Java可以优化条件的复杂程度吗?)
答案 0 :(得分:2)
有一个差异,尽管差异很小。关键问题是,该过程的任何步骤是否都由足够智能的软件完成,可以推断出如果x==0
,x==2
和x==5
必须为假。
在Java字节码级别,它们通常 产生不同的结果。编译器没有足够的聪明来分析差异是没有义务的。 (Eugene对相关问题的answer显示,Sun的Java 12编译器确实足够聪明,在某些情况下可以为您进行优化)
及时,编译器往往相当激进。他们更有可能意识到代码只能流经三个分支之一并对其进行优化。但这仍然是依赖于工具的声明。 Java语言本身将它们视为不同。
现在,实际上,除非您进行非常紧密的循环,否则这几乎没有关系。优化中的第一条规则是“先配置然后优化”。至少有99%的案例没有理由优化这些细节。
具体来说,在您提供的示例中,即使编译器和JIT无法为您优化代码,性能成本也可以忽略不计。在“平均” CPU上,成功预测的分支大约是函数调用成本的十分之一,因此,您在这些分支上调用doSomething()
的事实将使该成本相形见war。如果这些额外的调用导致了一些额外的分支错误预测,您可能会看到更糟糕的结果,但是却没有调用函数的事实那么昂贵。
现在,假设doSomething()
实际上是诸如x += 1
之类的快速内容的占位符,那么您需要进行分析以确定它是否正确。
因此,我的建议是根据正确中的任何一个写if/if/if
或if/else if/else if
。正确的答案是最适合您要使用的逻辑的一种。如果打算将其用作仅采用一条路径的分支,则建议使用else if
。如果打算在将来该函数可能执行许多分支,但恰好发生这种情况,即当前分支列表是互斥的,请执行if/if/if
以将预期结果传达给读者。 / p>
然后选择个人资料。一律剖析。如果您发现此功能是一个热点,请 then 考虑担心if语句是否昂贵。
顺便说一句,编译器很难证明他们可以将if
转换为else if
。它必须对x进行分析,以查看是否有可能另一个线程对其进行修改。如果它是局部变量,则没有其他线程可以修改它。但是,如果它是成员变量,则另一个线程可能会在x
块的中间修改if/if/if
,从而使其采用两条路径。您可能知道,没有其他人会像这样修改x
,但是编译器必须先进行证明,然后再进行这种优化,或者至少证明其编写的内容与实现相同Java内存模型的规则。
答案 1 :(得分:2)
好吧,只有适当的JMH测试才能证明某种方法有多快。当然,需要警告的是,如果您真的想知道为什么为什么,数字应该是数字的样子,那么您也应该了解底层的机器代码。我把这个留给您,只是在此测试中在此处显示数字,仅向您稍微说明一些细节。
package com.so;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
@Warmup(iterations = 5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Measurement(iterations = 2, time = 2, timeUnit = TimeUnit.SECONDS)
public class IfElseCompare {
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder()
.include(IfElseCompare.class.getName())
.jvmArgs("-ea")
.build();
new Runner(opt).run();
}
private int resolveValueMultipleIfs(IfElseExecutionPlan plan) {
int x = -1;
if (plan.value() == 0) {
x = 0;
}
if (plan.value() == 1) {
x = 1;
}
if (plan.value() == 2) {
x = 2;
}
assert x != -1;
return x;
}
private int resolveValueIfElse(IfElseExecutionPlan plan) {
int x = -1;
if (plan.value() == 0) {
x = 0;
} else if (plan.value() == 1) {
x = 1;
} else if (plan.value() == 2) {
x = 2;
}
assert x != -1;
return x;
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(1)
public int multipleIf(IfElseExecutionPlan plan) {
return resolveValueMultipleIfs(plan);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(1)
public int ifElse(IfElseExecutionPlan plan) {
return resolveValueIfElse(plan);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value = 1, jvmArgsAppend = "-Xint")
public int multipleIfsfNoJIT(IfElseExecutionPlan plan) {
return resolveValueMultipleIfs(plan);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value = 1, jvmArgsAppend = "-Xint")
public int ifElseNoJIT(IfElseExecutionPlan plan) {
return resolveValueIfElse(plan);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value = 1, jvmArgsAppend = "-XX:-TieredCompilation")
public int multipleIfsC2Only(IfElseExecutionPlan plan) {
return resolveValueMultipleIfs(plan);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value = 1, jvmArgsAppend = "-XX:-TieredCompilation")
public int ifElseC2Only(IfElseExecutionPlan plan) {
return resolveValueIfElse(plan);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value = 1, jvmArgsAppend = "-XX:TieredStopAtLevel=1")
public int multipleIfsC1Only(IfElseExecutionPlan plan) {
return resolveValueMultipleIfs(plan);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value = 1, jvmArgsAppend = "-XX:TieredStopAtLevel=1")
public int ifElseC1Only(IfElseExecutionPlan plan) {
return resolveValueIfElse(plan);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value = 1,
jvmArgsAppend = {
"-XX:+UnlockExperimentalVMOptions",
"-XX:+EagerJVMCI",
"-Dgraal.ShowConfiguration=info",
"-XX:+UseJVMCICompiler",
"-XX:+EnableJVMCI"
})
public int multipleIfsGraalVM(IfElseExecutionPlan plan) {
return resolveValueMultipleIfs(plan);
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@Fork(value = 1,
jvmArgsAppend = {
"-XX:+UnlockExperimentalVMOptions",
"-XX:+EagerJVMCI",
"-Dgraal.ShowConfiguration=info",
"-XX:+UseJVMCICompiler",
"-XX:+EnableJVMCI"
})
public int ifElseGraalVM(IfElseExecutionPlan plan) {
return resolveValueIfElse(plan);
}
}
结果如下:
IfElseCompare.ifElse avgt 2 2.826 ns/op
IfElseCompare.multipleIf avgt 2 3.061 ns/op
IfElseCompare.ifElseC1Only avgt 2 3.927 ns/op
IfElseCompare.multipleIfsC1Only avgt 2 4.397 ns/op
IfElseCompare.ifElseC2Only avgt 2 2.507 ns/op
IfElseCompare.multipleIfsC2Only avgt 2 2.428 ns/op
IfElseCompare.ifElseGraalVM avgt 2 2.587 ns/op
IfElseCompare.multipleIfsGraalVM avgt 2 2.854 ns/op
IfElseCompare.ifElseNoJIT avgt 2 232.418 ns/op
IfElseCompare.multipleIfsfNoJIT avgt 2 303.371 ns/op
如果使用多个if
条件反编译该版本,则:
0x000000010cf8542c: test %esi,%esi
0x000000010cf8542e: je 0x000000010cf8544f ;*ifne {reexecute=0 rethrow=0 return_oop=0}
; - com.so.IfElseCompare::resolveValueMultipleIfs@3 (line 21)
0x000000010cf85430: cmp $0x1,%esi
0x000000010cf85433: je 0x000000010cf8545e ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0}
; - com.so.IfElseCompare::resolveValueMultipleIfs@10 (line 25)
0x000000010cf85435: cmp $0x2,%esi
0x000000010cf85438: je 0x000000010cf8546e ;*if_icmpne {reexecute=0 rethrow=0 return_oop=0}
; - com.so.IfElseCompare::resolveValueMultipleIfs@17 (line 29)
一系列cmp/je
-比较并跳转,如果相等,很好,非常期望。
if/else
的反编译代码与一样(我会让你反编译并亲眼看到);使用(java-12)生成的ASM代码:
java -XX:+UnlockDiagnosticVMOptions
-XX:CICompilerCount=2
-XX:-TieredCompilation
"-XX:CompileCommand=print,com/so/IfElseCompare.resolveValueMultipleIfs"
com.so.IfElseCompare
答案 2 :(得分:1)
没有什么比老式的计时测试更胜一筹了
long total = 0;
long startTime;
long endTime;
for (int j = 0; j < 10; j++) {
startTime = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
if (i % 3 == 0) total += 1;
if (i % 3 == 1) total += 2;
if (i % 3 == 2) total += 3;
}
endTime = System.currentTimeMillis();
System.out.println("If only: " + (endTime - startTime));
startTime = System.currentTimeMillis();
for (int i = 0; i < 100000000; i++) {
if (i % 3 == 0) total += 1;
else if (i % 3 == 1) total += 2;
else if (i % 3 == 2) total += 3;
}
endTime = System.currentTimeMillis();
System.out.println("If-else: " + (endTime - startTime));
}
System.out.println(total);
(“总”值对于防止编译器删除整个循环是必不可少的!)
输出:
If only: 215
If-else: 137
If only: 214
If-else: 121
If only: 210
If-else: 120
If only: 211
If-else: 120
If only: 211
If-else: 121
If only: 210
If-else: 121
If only: 210
If-else: 121
If only: 211
If-else: 120
If only: 211
If-else: 120
If only: 211
If-else: 121
3999999980
我们可以看到,即使if条件显然是互斥的,if-else块的运行速度也显着提高。由于两个循环所用的时间长度不同,因此每个循环的编译代码必须不同。显然,编译器不会对此进行优化。 JIT或CPU分支预测也不完全。仍然有很大的区别。
我的建议:尽可能使用If-else
编辑:我还尝试了交换两个循环并获得了相同的结果。 If-else,要快得多。
编辑2:我在整个测试中添加了一个for循环,以消除初始化或预热中的任何差异。结果是一样的。
答案 3 :(得分:0)
让我告诉您条件运算符“ if()”的工作原理。当您编写if()语句时,它将检查您在这些“()”中提供的条件的真实性。如果条件失败,则编译器将寻找if()条件失败时可以使用的替代语句或代码块。现在,对于此替代内容,我们使用“ else”块。
现在根据您的问题,答案很容易理解。两种方式都有很大的区别。
1)。多个If语句
if (x == 0) doSomething();
if (x == 2) doSomething();
if (x == 5) doSomething();
在上面的代码中,无论是否满足任何条件,编译器都会解析所有if语句。因为它们是分开使用的,没有任何替代部分。
2)。链式If-else语句
if (x == 0) doSomething();
else if (x == 2) doSomething();
else if (x == 5) doSomething();
现在在上面的代码中,现在有一个主要的状态检查器(x==0)
,如果失败了,那么还有其他选择,因此编译器将对其进行检查,直到找到满意的解决方案为止。
性能问题
在第一种情况下,编译器必须检查每个条件,因为它们都是独立的,这可能需要更多时间。但是在第二种情况下,仅当if()语句不满足条件时,才编译“ else if”部分。因此,在性能方面,它们之间可能会有一些差异。
我希望它会有所帮助。