JVM循环优化与向量化优化

即时编译器针对循环程序块的编译优化,生成的IR图会改变原有的循环程序块内容 — 外提与展开。(外提就是提取公因式,展开就是减少判断次数)

循环优化一:无关代码外提 — 减少某些程序的重复执行

即时编译器会将常值放到循环体外,并且计算一次后会将这些常值放入缓存,每次循环直接从缓存中取数据。

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 fooManualOpt(int x, int y, int[] a) {
  int sum = 0;
  int t0 = x * y;  // x*y每次在循环体中都是不变的,放到循环外只需要计算一次就可以了
  int t1 = a.length;  // 数组的长度保存在数组对象的对象头中
  for (int i = 0; i < t1; i++) {
    sum += t0 + a[i];
  }
  return sum;
}

循环优化二:循环展开 — 减少for判断的次数

循环体不一定会按照既定的增长幅度进行(i++可能会变成i+=2),也就是会将多次循环合并到一次循环中执行(部分展开:具体合并几个循环得看即时编译器优化结果),当循环次数比较少时,可能会取消循环体(完全展开),直接顺序执行几条指令。

循环调用会消耗CPU资源(判断是否满足循环条件),循环展开会增加代码的冗余度(编译成机器码会变多),所以即时编译器会进行权衡选择合适的展开程度。

int foo(int[] a) {
  int sum = 0;
  for (int i = 0; i < 64; i++) {
    sum += (i % 2 == 0) ? a[i] : -a[i];
  }
  return sum;
}

/** 优化完后如下 */

int foo(int[] a) {
  int sum = 0;
  for (int i = 0; i < 64; i += 2) { // 注意这里的步数是2
    sum += (i % 2 == 0) ? a[i] : -a[i];
    sum += ((i + 1) % 2 == 0) ? a[i + 1] : -a[i + 1];
  }
  return sum;
}

  • 循环展开的条件:空间换时间

1. 循环下标数据类型是int、short、或者char;for(int i=0; i < max; i++ )
2. 循环上限是与循环无关的;for(int i=0; i < max; i++ )
3. 循环增长步长为固定值;for(int i=0; i < max; i++ )

循环优化三:循环判断外提 — 减少if判断次数

将循环体中的if程序块放到循环外(要保证不影响程序原语义)。

int foo(int[] a) {
  int sum = 0;
  for (int i = 0; i < a.length; i++) {
    if (a.length > 4) {
      sum += a[i];
    }
  }
  return sum;
}

/** 优化完后如下 */

int foo(int[] a) {
  int sum = 0;
  if (a.length > 4) {
    for (int i = 0; i < a.length; i++) {
      sum += a[i];
    }
  } else {
    for (int i = 0; i < a.length; i++) {
    }
  }
  return sum;
}
// 进一步优化为:
int foo(int[] a) {
  int sum = 0;
  if (a.length > 4) {
    for (int i = 0; i < a.length; i++) {
      sum += a[i];
    }
  }
  return sum;
}

循环优化四:循环剥离 — 尽量让循环体内处理的逻辑一致,没有特殊情况

将循环的首尾几次循环放到循环外,因为通常情况下,循环开始于结束位置我们会进行特殊的额外处理(比如边界处理),剥离后中间的循环体每次的逻辑都是一样的,可以提高执行效率。

int foo(int[] a) {
  int j = 0;
  int sum = 0;
  for (int i = 0; i < a.length; i++) {  // 将i=0提出
    sum += a[j];
    j = i;
  }
  return sum;
}

/** 优化完后如下 */

int foo(int[] a) {
  int sum = 0;
  if (0 < a.length) {
    sum += a[0];  // 特殊操作第一次循环,提高循环体外面
    for (int i = 1; i < a.length; i++) {
      sum += a[i - 1];
    }
  }
  return sum;
}

向量化优化

包括HotSpot intrinsic与自动向量化:主要就是指将多次对数据的操作指令放入到单次CPU操作中。也就是SIMD指令:基于大长度的寄存器实现一次操作能读写更多的数据(X86默认寄存器是64位,8字节,也就是一次读写数据最多8字节,但是现在有很多其他的支持更多字节的寄存器),所以CPU执行一次操作可以处理更多的指令。

结合上述循环展开,可以将多行循环体操作放到同一个CPU指令中执行。

参考文章:

https://time.geekbang.org/column/article/39814

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值