循环在Java里是不可避免也是经常使用到的,循环一个数组集合非常常用。如何写一个安全简洁高效的循环呢?
-
循环无关代码外提:是指在一个循环里不发生改变的表达式比如数组长度,当然这是要不影响整个循环的语义。
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;
}
-
在上文这个示例里,让我们看一下那些是没有在循环里发生改变的。
-
首先很明显a.length在每次循环都是不变的值。
-
其次是x*y 的值也没有发生改变。那就可以做一次优化.
int foo(int x,int y,int[] a){ int sum=x*y; for(int i=0 ,len=a.length;i<len;i++) { sum+=a[i] } return sum; }
-
这是代码层面所做的优化,编译器能够如何进一步优化呢?
- int 数组加载指令iaload 的null 检测及数组越界检测(range check)。数组和下标作为参数,伪代码如下
int iaload(int[] a,int i){
if(null==a){
throw new NullException();
}
if(i<0||i>=a.length){
throw new ArrayIndexOutOfBoundsException();
}
return a[i]
}
-
null 检测外提优化,是C2即时编译器通过循环预测来进行优化。即在循环外进行null 检测,命中测可消除循环内的null检测。
-
数组越界检测则是通过检测i的最小值和i的最大值和数组长度比较进行外提到循环外。i能达到的最大值不一定等于数组长度减一,这是因为步长,即i++可能是i+2,也可能在循环内改变i的值。伪代码
// in能达到的最大值MAX,len数组长度 if(i<0||MAX>=len){ throw new IndexOutOfBoundsException(); } for...
循环展开是另一项循环优化方式:即对循环的次数进行进行一定次的展开。一个示例:
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) {
sum += (i % 2 == 0) ? a[i] : -a[i];
sum+=((i+1)%2==0)?a[i+1]:-a[i+1]
}
return sum;
}
- 进一步可以优化为:很明显i从0 开始,每次加2,则i%2 每次都是0,(i+1)%2 则都是1
int foo(int[] a) {
int sum = 0;
for (int i = 0; i < 64; i+=2) {
sum += a[i]
sum +=-a[i+1]
}
return sum;
}
-
循环完全展开,如果循环的次数比较小,则可以进行完全展开。即不适用循环。
-
循环判断外提 :也可以进行优化循环,循环里的判断放在循环外进行。
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]; } } 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]; } } return sum; }
本文参考自循环优化 2018-09-17 郑雨迪