Java:手动展开的循环仍然比原始循环快。为什么?
考虑长度为 2 的数组上的以下两个代码段:
boolean isOK(int i) {
for (int j = 0; j < filters.length; ++j) {
if (!filters[j].isOK(i)) {
return false;
}
}
return true;
}
和
boolean isOK(int i) {
return filters[0].isOK(i) && filters[1].isOK(i);
}
我认为在充分预热后,这两件作品的性能应该相似。
我已经使用JMH微基准测试框架对此进行了检查,例如在这里和这里,并观察到第二个代码段的速度提高了10%以上。
问:为什么 Java 没有使用基本循环展开技术优化我的第一个代码段?
特别是,我想了解以下内容:
- 我可以很容易地生成一个代码,该代码对于2个过滤器的情况是最佳的,并且在另一个过滤器的情况下仍然可以工作(想象一个简单的构建器):
。JITC可以做同样的事情吗,如果没有,为什么?return (filters.length) == 2 ? new FilterChain2(filters) : new FilterChain1(filters)
- JITC能否检测到“filters.length==2”是最常见的情况,并在预热后生成最适合这种情况的代码?这应该几乎与手动展开的版本一样最佳。
- JITC 能否检测到特定实例的使用非常频繁,然后为此特定实例生成代码(它知道筛选器的数量始终为 2)?
更新:得到一个答案,JITC只在类级别工作。好的,知道了。
理想情况下,我希望从对JITC的工作原理有深刻理解的人那里得到答案。
基准测试运行详细信息:
- 在最新版本的Java 8 OpenJDK和Oracle HotSpot上尝试过,结果相似
- 使用的 Java 标志: -Xmx4g -Xms4g -server -Xbatch -XX:CICompilerCount=2 (在没有花哨标志的情况下也得到了类似的结果)
- 顺便说一句,如果我只是在一个循环中运行几十亿次(不是通过JMH),我会得到类似的运行时间比率,即第二个片段总是明显更快
典型基准测试输出:
Benchmark (filterIndex) Mode Cnt Score Error Units
LoopUnrollingBenchmark.runBenchmark 0 avgt 400 44.202 ± 0.224 ns/op
LoopUnrollingBenchmark.runBenchmark 1 avgt 400 38.347 ± 0.063 ns/op
(第一行对应于第一个代码段,第二行对应于第二个代码段。
完整的基准测试代码:
public class LoopUnrollingBenchmark {
@State(Scope.Benchmark)
public static class BenchmarkData {
public Filter[] filters;
@Param({"0", "1"})
public int filterIndex;
public int num;
@Setup(Level.Invocation) //similar ratio with Level.TRIAL
public void setUp() {
filters = new Filter[]{new FilterChain1(), new FilterChain2()};
num = new Random().nextInt();
}
}
@Benchmark
@Fork(warmups = 5, value = 20)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int runBenchmark(BenchmarkData data) {
Filter filter = data.filters[data.filterIndex];
int sum = 0;
int num = data.num;
if (filter.isOK(num)) {
++sum;
}
if (filter.isOK(num + 1)) {
++sum;
}
if (filter.isOK(num - 1)) {
++sum;
}
if (filter.isOK(num * 2)) {
++sum;
}
if (filter.isOK(num * 3)) {
++sum;
}
if (filter.isOK(num * 5)) {
++sum;
}
return sum;
}
interface Filter {
boolean isOK(int i);
}
static class Filter1 implements Filter {
@Override
public boolean isOK(int i) {
return i % 3 == 1;
}
}
static class Filter2 implements Filter {
@Override
public boolean isOK(int i) {
return i % 7 == 3;
}
}
static class FilterChain1 implements Filter {
final Filter[] filters = createLeafFilters();
@Override
public boolean isOK(int i) {
for (int j = 0; j < filters.length; ++j) {
if (!filters[j].isOK(i)) {
return false;
}
}
return true;
}
}
static class FilterChain2 implements Filter {
final Filter[] filters = createLeafFilters();
@Override
public boolean isOK(int i) {
return filters[0].isOK(i) && filters[1].isOK(i);
}
}
private static Filter[] createLeafFilters() {
Filter[] filters = new Filter[2];
filters[0] = new Filter1();
filters[1] = new Filter2();
return filters;
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}