为了提升循环的效率,编译器会针对循环的编译进行多种方式的优化,比如:循环无关代码外提,循环展开等
1.循环无关代码外提
- 循环无关代码:循环中值不会发生变化的表达式。通过将这些循环无关的代码提出循环外,可避免重复执行这些表达式,实现性能提升。
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;
}
循环无关代码:(1)表达式 :x * y (2)循环判断条件:a.length
优化后代码如下:
int foo(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;
2.循环展开
- 循环展开:在循环体中重复多次循环迭代,并减少循环次数的编译优化。
int foo(int x, int y, 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 x, int y, 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;
}
当循环的数目是固定值,且非常小时,即时编译器会将循环全部展开,此时,原本循环中的循环判断语句就不存在了,取而代之的是若干个顺序执行的循环体。
int foo(int[] a) {
int sum = 0;
for( int i = 0 ; i<4 ;i++ ) {
sum += a[i];
}
return sum;
}
完全展开后
int foo(int[] a) {
int sum = 0;
sum += a[0];
sum += a[1];
sum += a[2];
sum += a[4];
return sum;
}
即使编译器会在循环体的大小和循环展开次数之间做出权衡。
3.循环判断外提
- 将循环中的if语句外提至循环之前,并且在该if语句的两个分支中分别放置一份循环代码
int foo(int[] a) {
int sum = 0;
for( int i = 0 ; i<a.length ;i++ ) {
if(a.length > 4) {
sum += a[i];
} else {
sum += a[i] + 1;
}
}
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++ ) {
sum += a[i] + 1;
}
}
return sum;
}
4.循环剥离
- 循环剥离:将循环的前几个迭代或者后几个迭代剥离出循环的优化方式。背景是循环的前几个迭代或后几个迭代都包含特殊处理。通过将这几个特殊的迭代逻辑剥离后,原本的循环体的规律性更加明显,从而触发进一步的优化。
int foo(int[] a) {
int sum = 0;
int j = 0;
for( int i = 0 ; i<a.length ;i++ ) {
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;
}