深入拆解Java虚拟机
郑雨迪
Oracle 高级研究员,计算机博士
立即订阅
27943 人已学习
课程目录
已完结 39 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 为什么我们要学习Java虚拟机?
免费
模块一:Java虚拟机基本原理 (12讲)
01 | Java代码是怎么运行的?
02 | Java的基本类型
03 | Java虚拟机是如何加载Java类的?
04 | JVM是如何执行方法调用的?(上)
05 | JVM是如何执行方法调用的?(下)
06 | JVM是如何处理异常的?
07 | JVM是如何实现反射的?
08 | JVM是怎么实现invokedynamic的?(上)
09 | JVM是怎么实现invokedynamic的?(下)
10 | Java对象的内存布局
11 | 垃圾回收(上)
12 | 垃圾回收(下)
模块二:高效编译 (12讲)
【工具篇】 常用工具介绍
13 | Java内存模型
14 | Java虚拟机是怎么实现synchronized的?
15 | Java语法糖与Java编译器
16 | 即时编译(上)
17 | 即时编译(下)
18 | 即时编译器的中间表达形式
19 | Java字节码(基础篇)
20 | 方法内联(上)
21 | 方法内联(下)
22 | HotSpot虚拟机的intrinsic
23 | 逃逸分析
模块三:代码优化 (10讲)
24 | 字段访问相关优化
25 | 循环优化
26 | 向量化
27 | 注解处理器
28 | 基准测试框架JMH(上)
29 | 基准测试框架JMH(下)
30 | Java虚拟机的监控及诊断工具(命令行篇)
31 | Java虚拟机的监控及诊断工具(GUI篇)
32 | JNI的运行机制
33 | Java Agent与字节码注入
模块四:黑科技 (3讲)
34 | Graal:用Java编译Java
35 | Truffle:语言实现框架
36 | SubstrateVM:AOT编译框架
尾声 (1讲)
尾声 | 道阻且长,努力加餐
深入拆解Java虚拟机
登录|注册

25 | 循环优化

郑雨迪 2018-09-17
在许多应用程序中,循环都扮演着非常重要的角色。为了提升循环的运行效率,研发编译器的工程师提出了不少面向循环的编译优化方式,如循环无关代码外提,循环展开等。
今天,我们便来了解一下,Java 虚拟机中的即时编译器都应用了哪些面向循环的编译优化。

循环无关代码外提

所谓的循环无关代码(Loop-invariant Code),指的是循环中值不变的表达式。如果能够在不改变程序语义的情况下,将这些循环无关代码提出循环之外,那么程序便可以避免重复执行这些表达式,从而达到性能提升的效果。
int foo(int x, int y, int[] a) {
int sum = 0;
for (int i = 0; i < a.length; i++) {
sum += x * y + a[i];
}
return sum;
}
// 对应的字节码
int foo(int, int, int[]);
Code:
0: iconst_0
1: istore 4
3: iconst_0
4: istore 5
6: goto 25
// 循环体开始
9: iload 4 // load sum
11: iload_1 // load x
12: iload_2 // load y
13: imul // x*y
14: aload_3 // load a
15: iload 5 // load i
17: iaload // a[i]
18: iadd // x*y + a[i]
19: iadd // sum + (x*y + a[i])
20: istore 4 // sum = sum + (x*y + a[i])
22: iinc 5, 1 // i++
25: iload 5 // load i
27: aload_3 // load a
28: arraylength // a.length
29: if_icmplt 9 // i < a.length
// 循环体结束
32: iload 4
34: ireturn
举个例子,在上面这段代码中,循环体中的表达式x*y,以及循环判断条件中的a.length均属于循环不变代码。前者是一个整数乘法运算,而后者则是内存访问操作,读取数组对象a的长度。(数组的长度存放于数组对象的对象头中,可通过 arraylength 指令来访问。)
理想情况下,上面这段代码经过循环无关代码外提之后,等同于下面这一手工优化版本。
int fooManualOpt(int x, int y, int[] a) {
int sum = 0;
int t0 = x * y;
int t1 = a.length;
for (int i = 0; i < t1; i++) {
sum += t0 + a[i];
}
return sum;
}
我们可以看到,无论是乘法运算x*y,还是内存访问a.length,现在都在循环之前完成。原本循环中需要执行这两个表达式的地方,现在直接使用循环之前这两个表达式的执行结果。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《深入拆解Java虚拟机》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(11)

  • godtrue
    现在浏览器终于也可以写留言了,非常好!希望能将和老师相互的讨论的功能也开开,否则,不能进行对话,讲某些问题的效果不太好!

    循环优化,站在编译器的角度来作出的优化动作,老师介绍了几种方式,经过听讲,我感觉万变不离其宗,优化的核心关键点还是少做一些事情,当然,事情少做了,作用不能减!

    1:循环无关码外提——将循环内的某些无关代码外移,减少某些程序的反复执行
    2:循环展开——减少循环条件的判断,针对循环次数少的循环
    3:循环判断外提——减少每次循环的都进行判断次数
    4:循环剥离——将不通用的处理起来稍微费劲一些的动作,放在循环外处理

    总之,要做减法!
    性能优化的核心点:
    1:让做的快的做
    2:如果不能实现,则让做的快的做多一点,做的慢的少做一些
    3:取巧,事情少做了,但是目的依旧能够达到

    作者回复: 对的。在程序语义不改变的情况下,编译器会尽可能地减少生成代码的工作量。

    2018-09-17
    8
  • Geek_488a8e
    这些都是DSP代码典型的优化方法,目的是防止打断CPU的指令流水,提高指令处理的并行度

    作者回复: Good to know

    2018-09-19
    5
  • Len
    老师,如果有这样一段代码:

    for( ... ) {
       sum += x + y + a[i];
    }

    借助 Sea-of-Nodes IR 能把「x + y」表达式外提出去。

    但,如果表达式变成如下:

    sum += x + a[i] + y;

    也能借助 IR 外提 「x + y」吗?

    作者回复: 赞想法!会的。

    2018-09-18
    4
  • 一个坏人
    是不是写应用系统的时候没必要按照优化方式写,编译器反正会优化?!

    作者回复: 很多情况下是的。但也要考虑编译器没有预算来做优化的情况(比如循环太大)。

    一般来说,应用代码更应注重可读性。

    2018-09-17
    1
  • Scott
    这样展开后有一个强度削弱的机会,四个byte的赋值合并成一个int?

    作者回复: 对的!不叫强度削弱,叫向量化,下一篇讲

    2018-09-17
    1
  • 无言的约定
    for (int i = INIT; i < LIMIT; i += STRIDE) {
      if (i < 0 || i >= a.length) { // range check
        throw new ArrayIndexOutOfBoundsException();
      }
      sum += a[i];
    }
    ----------
    // 经过下标范围检测外提之后:
    if (INIT < 0 || IMAX >= a.length) {
      // IMAX 是 i 所能达到的最大值,注意它不一定是 LIMIT-1
      detopimize(); // never returns
    }
    for (int i = INIT; i < LIMIT; i += STRIDE) {
      sum += a[i]; // 不包含下标范围检测
    }
    老师,这个IMAX该如何初始化?
    2019-10-12
  • Yoph
    这些优化全都是即时编译器做的,解释器的执行过程中有相关的优化吗?
    2019-07-16
  • 天之蓝
    请教两个问题,循环展开那个例子如果64是65是不是就越界了?实践的代码如果length为6按条件只会循环一次那下标为4、5的不就执行不到了吗?
    2018-11-28
  • Leon Wong
    请问老师,实践环节的循环展开后的数组越界,编译器是怎么处理的?是不是当length小于4,循环完全展开就可以了,实际上这个展开有一个隐含的假定,即length大于4的情况。

    作者回复: 对的,如果是常量长度,而且小于4,那么完全展开就行了。

    2018-10-07
  • 白三岁
    实践环节的代码,由于i++相应的变成了i+4。前面的判断条件dst.length就不应该减4了吧。

    作者回复: 观察到位!这个主要是为了避免访问越界。你可以假定length为3,再看看这段代码。

    2018-09-28
  • 杨春鹏
    循环展开优化,如何防止出现数组下边越界?
    Length=3n+2,每次循环展开n,n+1,n+2,当第n次循环结束的时候,下标开始从3n+1、3n+2、3n+3,那么访问3n+2与3n+3对应值时,就会出现数组越界。
    2018-09-19
收起评论
11
返回
顶部