编程效率篇

1. switch 语句

当我们对某个语句求值,而结果有很多可能性时,这个时候用 ifelse 时会显得代码阅读性很差,并且很繁琐。switch语句应运而生,编译后底层维持的是一个跳转表。

示例

int x;
int result;
switch( x ){
    case 101:    //L1
        result = 1;
        break;
    case 104:   //L2
        result = 2;
        break;
    case 105:   //L3
    case 106:   
        result = 3;
        break;
    default:
        result = 4;
        break;
}

显然这样写是非常简便的。汇编后维护的跳转表如下:

index n 0 1 2 3 4 5
int x 101 102 103 104 105 106
case L1 default default L2 L3 L3

C中维护的跳转表示一个数组,将x映射到数组下标n,直接跳转。
但是可以发现,在switch中,即使没有case 102, 103在跳转表中任然为这两个分配到了default,维护的是一块连续的内存区域。因此当switch中,有时候,x的值即使不可能为102,103,当这种情况较少时,在我们能接受的范围之内。但当x跨越的值范围较大,且缺省的值较多时,会造成很多的内存浪费情况。
因此对于switch语句,编译器最终采取编译为 ifelse 还是跳转表形式,取决于对内存和速度的权衡。

2.条件传送语句

语法: testexpr ? expr1 : expr2
可能很多人刚开始学这的时候只会简单的误以为,这和下面的 ifelse 等价:

if(test-expr)
    return expr1;
else
    return expr2;
  • 执行效率:在条件传送语句中,语句执行的顺序如下:

    而在简单的 ifelse ,语句执行的顺序如下:
    编程效率篇_第1张图片
    在现代的处理器中,采用的是流水线架构来获得高性能。假设将处理器执行分为以下5个阶段:从存储器取指令,确定指令的类型,从存储器读数据,执行算术运算,向存储器中写数据以及更新程序计数器。流水线采用的就是连续执行指令,同一个周期内,执行不同指令的不同周期,充分利用各部件。因此处理器需要塞满流水线的话,在遇到条件控制时,由于难以准确地知道下一条指令应该执行哪一条,一般采取的是分支预测逻辑来试图猜测下一条指令会跳向哪个地方(现在处理器的预测准确率达到90%以上)。在上例 ifelse 中,它也许会猜测下一条指令是 expr1 ,假如它猜测正确了,那皆大欢喜;假如猜测错误,那可能就要丢掉它为错误指令所做的工作,重新执行 expr2 。这样的惩罚可能是20~40个周期的浪费,从而导致程序性能的严重下降。但是在 testexpr ? expr1 : expr2 这种条件传送指令中,对于 expr1 expr2 都已经提前算好了。这样的话,每次判断 testexpr 时,如果它为真,则更新返回值,如果为假,则保持不变。在这个过程中无需预测 testexpr 的结果,程序计数器也不用预测。
    但是,使用条件转送也并不总会改善程序的性能,特别是当 expr1 expr2 计算量很大时,这样必然会带来性能的浪费,因为其中有一个值是没有用的。
  • 语句执行带来的副作用
    举下面这个例子:
    ifelse:
if(x)
    return *x;
else
    return 0;

return x ? *x : 0;

可以发现,在条件传送中,不管 x 是否为空指针, x 都会执行。这是违法的。

实验性能比较

package ProgramTips;
/** * Created by Wang on 2016/1/24. * this program is used to compare if-else and test-expr?expr1:expr2 */
public class IfElse {
    static int N;
    double a[];
    double b[];
    double c[];
    double d[];
    public IfElse(int N){
        this.N = N;
        a = new double[N];
        b = new double[N];
        c = new double[N];
        d = new double[N];

    }

    public void Test1(){
        for(int iter = 0; iter < 300; iter++) {
            for (int i = 0; i < N; i++) {
                a[i] = Math.random();
                b[i] = Math.random();
            }
            for (int i = 0; i < N; i++) {
                if (a[i] > b[i])
                    c[i] = Math.pow(a[i], b[i]);
                else
                    c[i] = Math.pow(b[i] ,a[i]);
            }
        }
    }


    public void Test2(){
        for(int iter = 0; iter < 300; iter++) {
            for (int i = 0; i < N; i++) {
                a[i] = Math.random();
                b[i] = Math.random();
            }
            for (int i = 0; i < N; i++) {
                d[i] = a[i] > b[i] ?  Math.pow(a[i], b[i]) : Math.pow(b[i] ,a[i]);
            }
        }
    }

    public void Test3(){
        for(int iter = 0; iter < 300; iter++) {
            for (int i = 0; i < N; i++) {
                a[i] = Math.random();
                b[i] = Math.random();
            }
            for (int i = 0; i < N; i++) {
                if (a[i] > b[i])
                    c[i] = Math.pow(a[i], b[i])*(a[i] - b[i])/a[i];
                else
                    c[i] = Math.pow(b[i] ,a[i])*(a[i] - b[i])/b[i];
            }
        }
    }

    public void Test4(){
        for(int iter = 0; iter < 300; iter++) {
            for (int i = 0; i < N; i++) {
                a[i] = Math.random();
                b[i] = Math.random();
            }
            for (int i = 0; i < N; i++) {
                d[i] = a[i] > b[i] ?  Math.pow(a[i], b[i])*(a[i] - b[i])/a[i] : Math.pow(b[i] ,a[i])*(a[i] - b[i])/b[i];
            }
        }
    }

    public static void main(String[] args){
        IfElse ie = new IfElse(5000000);
        double t1 = System.currentTimeMillis();
        ie.Test1();
        t1 = System.currentTimeMillis() - t1;
        double t2 = System.currentTimeMillis();
        ie.Test2();
        t2 = System.currentTimeMillis() - t2;
        double t3 = System.currentTimeMillis();
        ie.Test3();
        t3 = System.currentTimeMillis() - t3;
        double t4 = System.currentTimeMillis();
        ie.Test4();
        t4 = System.currentTimeMillis() - t4;
        System.out.println(t1);
        System.out.println(t2);
        System.out.println(t3);
        System.out.println(t4);
    }
}

时间:

t1 t2 t3 t4
if-else(expr简单) 条件传送(expr简单) if-else(expr复杂) 条件传送(expr复杂)
280425.0 278376.0 284522.0 302166.0

可以看到,在expr简单的情况下,条件传送性能更好;在expr复杂的情况下,条件传送性能反而不如 ifelse 了。

结论

因此综上所述,条件传送指令其实也是在相当受限的情况下可以代替 ifelse 从而改善程序性能。

你可能感兴趣的:(编程,深入理解计算机系统)