Java:手动展开的循环仍然比原始循环快。为什么?

Java:手动展开的循环仍然比原始循环快。为什么?,java,performance,optimization,jit,Java,Performance,Optimization,Jit,考虑长度为2的数组上的以下两段代码: boolean isOK(int i) { for (int j = 0; j < filters.length; ++j) { if (!filters[j].isOK(i)) { return false; } } return true; } 我认为这两件作品在充分预热后的表现应该是相似的 我已经使用JMH micro benchmarking framework(

考虑长度为2的数组上的以下两段代码:

boolean isOK(int i) {
    for (int j = 0; j < filters.length; ++j) {
        if (!filters[j].isOK(i)) {
            return false;
        }
    }
    return true;
}
我认为这两件作品在充分预热后的表现应该是相似的
我已经使用JMH micro benchmarking framework(如所述)对此进行了检查,并发现第二个代码片段的速度快了10%以上

问题:为什么Java没有使用基本循环展开技术优化我的第一个代码段?
我特别想了解以下几点:

  • 我可以很容易地生成一个代码,该代码对于两个过滤器的情况是最佳的,并且在另一个过滤器的情况下仍然可以工作(想象一个简单的构建器):
    return(filters.length)==2?新过滤器链2(过滤器):新过滤器链1(过滤器)
    。JITC也可以这样做吗?如果不能,为什么
  • JITC能否检测到“filters.length==2”是最常见的情况,并在一些预热后生成最适合这种情况的代码?这应该和手动展开的版本一样好
  • JITC能否检测到一个特定的实例被频繁使用,然后为这个特定实例生成一个代码(它知道过滤器的数量总是2)
    更新:得到的答案是JITC只在类级别上工作。好的,明白了
  • 理想情况下,我希望能从对JITC工作原理有深入了解的人那里得到答案

    基准运行详细信息:

    • 在最新版本的Java8OpenJDK和OracleHotSpot上试用,结果类似
    • 使用的Java标志:-Xmx4g-Xms4g-server-Xbatch-XX:CICompilerCount=2(在没有特别标志的情况下也得到了类似的结果)
    • 顺便说一下,如果我只是在一个循环中运行它数十亿次(而不是通过JMH),我会得到类似的运行时间比率,也就是说,第二个代码段总是明显更快
    典型基准输出:

    基准(filterIndex)模式Cnt分数 错误单位
    LoopUnrollingBenchmark.runBenchmark 0 avgt 400 44.202±0.224纳秒/升
    LoopUnrollingBenchmark.runBenchmark 1 avgt 400 38.347 ±0.063纳秒/升

    (第一行对应于第一个代码段,第二行对应于第二个代码段

    完整的基准代码:

    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);
        }
    }
    
    public类LoopUnrollingBenchmark{
    @国家(范围、基准)
    公共静态类基准数据{
    公共过滤器[]过滤器;
    @参数({“0”,“1”})
    公共int过滤器索引;
    公共整数;
    @Setup(Level.Invocation)//与Level.TRIAL的比率相似
    公共作废设置(){
    filters=newfilter[]{newfilterchain1(),newfilterchain2()};
    num=新随机数().nextInt();
    }
    }
    @基准
    @叉子(预热=5,数值=20)
    @基准模式(模式平均时间)
    @输出时间单位(时间单位纳秒)
    public int runBenchmark(BenchmarkData数据){
    Filter Filter=data.filters[data.filterIndex];
    整数和=0;
    int num=data.num;
    if(filter.isOK(num)){
    ++总和;
    }
    if(filter.isOK(num+1)){
    ++总和;
    }
    if(filter.isOK(num-1)){
    ++总和;
    }
    if(filter.isOK(num*2)){
    ++总和;
    }
    if(filter.isOK(num*3)){
    ++总和;
    }
    if(filter.isOK(num*5)){
    ++总和;
    }
    回报金额;
    }
    接口滤波器{
    布尔isOK(inti);
    }
    静态类过滤器1实现过滤器{
    @凌驾
    公共布尔isOK(int i){
    返回i%3==1;
    }
    }
    静态类过滤器2实现过滤器{
    @凌驾
    公共布尔isOK(int i){
    返回i%7==3;
    }
    }
    静态类FilterChain1实现过滤器{
    最终过滤器[]过滤器=createLeafFilters();
    @凌驾
    公共布尔isOK(int i){
    对于(int j=0;j
    所显示的循环可能属于“未计数”循环类别,这些循环的迭代计数既不能在编译时确定,也不能在运行时确定。这不仅是因为关于数组大小的@Andreas参数,还因为随机条件的
    中断
    (当我写这篇文章时,这曾经是你的基准测试)

    最先进的编译器不会太过激进 优化它们,因为展开未计数的循环通常需要 同时复制循环的退出条件,这样只会改进 如果后续编译器优化可以 优化展开的代码。有关详细信息,请参阅本文,他们在哪里提出如何展开这些内容的建议

    由此可知,您的假设并不认为您进行了某种“手动展开”你认为这是一个基本的循环展开技术,把一个有条件中断的数组转换成一个<代码> & & <代码>链式布尔表达式。我会认为这是一个非常特殊的情况,会惊奇地发现热点优化器一下子就做了一个复杂的重构。
    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);
        }
    }
    
    if (! filters[0].isOK(i))
    {
       return false;
    } 
    if(! filters[1].isOK(i))
    {
       return false;
    }
    return true;
    
      if (!cl->has_exact_trip_count()) {
        // Trip count is not exact.
        return false;
      }
    
      // Don't unroll if the next round of unrolling would push us
      // over the expected trip count of the loop.  One is subtracted
      // from the expected trip count because the pre-loop normally
      // executes 1 iteration.
      if (UnrollLimitForProfileCheck > 0 &&
          cl->profile_trip_cnt() != COUNT_UNKNOWN &&
          future_unroll_ct        > UnrollLimitForProfileCheck &&
          (float)future_unroll_ct > cl->profile_trip_cnt() - 1.0) {
        return false;
      }