条件数据传送和条件控制转移

在阅读csapp这本书中关于第五章优化程序性能——条件数据传送和条件控制转移时,我陷入了一个误区就是:条件数据传送方法的关键汇编指令必然是cmov,条件控制转移方法的关键汇编指令必然是cmp。但对两种不同的方法编译后产生的汇编代码进行分析时却发现这个观点是错误的。

1.条件控制转移

根据条件的成立来决定程序的执行流程,涉及到分支操作。同时因为它的分支预测收益高,所以现在指令的流水线架构通常为它提供分支预测来提高综合性能。接下来的分析也会解释为什么它的收益。

//条件控制转移 a数组中i下标的所有元素坐标要比b小
void ControlTransfer(long a[],long b[],long n){
    for(long i = 0;i<n;i++){
        if(a[i] > b[i]){
            long t = a[i];
            a[i] = b[i];
            b[i] = t;
        }
    }
}

得到的汇编代码如下所示,关键指令已标出。

    0x004015c4 <+0>:     push   %ebp
   0x004015c5 <+1>:     mov    %esp,%ebp
   0x004015c7 <+3>:     sub    $0x10,%esp			//esp = esp - 16byte
=> 0x004015ca <+6>:     movl   $0x0,-0x4(%ebp)		// i =0
   0x004015d1 <+13>:    jmp    0x401647 <ControlTransfer+131> //判断i<n
   
   0x004015d3 <+15>:    mov    -0x4(%ebp),%eax		// eax = i
   0x004015d6 <+18>:    lea    0x0(,%eax,4),%edx	// 4i 此时为0
   0x004015dd <+25>:    mov    0x8(%ebp),%eax		//取出基值 *a
   0x004015e0 <+28>:    add    %edx,%eax			//eax = *a + 4i => a[0] 
   0x004015e2 <+30>:    mov    (%eax),%edx			//edx = *a 解析a[0]的值放入edx中
   
   0x004015e4 <+32>:    mov    -0x4(%ebp),%eax		//eax = i (0)
   0x004015e7 <+35>:    lea    0x0(,%eax,4),%ecx	//ecx = 4i = 0
   0x004015ee <+42>:    mov    0xc(%ebp),%eax		// 8(%ebp) + offset(4byte) = c(%ebp) 即*b
   0x004015f1 <+45>:    add    %ecx,%eax			//eax = *b + 4i => b[0]
   0x004015f3 <+47>:    mov    (%eax),%eax			//eax = *b 即b[0]
   
   0x004015f5 <+49>:    cmp    %eax,%edx			//比较eax=a[0]和ebx = b[0]  !--关键指令 --!
   0x004015f7 <+51>:    jle    0x401643 <ControlTransfer+127> //a[0] <= b[0] 条件码为小于等于时触发条件 jmp到0x401643判断循环是否越界
			
   0x004015f9 <+53>:    mov    -0x4(%ebp),%eax		//a[0]>b[0] 条件成立.开始交换a[0]和b[0]
   0x004015fc <+56>:    lea    0x0(,%eax,4),%edx	//edx = 4*0	
   0x00401603 <+63>:    mov    0x8(%ebp),%eax		//eax = *a
   0x00401606 <+66>:    add    %edx,%eax
   0x00401608 <+68>:    mov    (%eax),%eax			//eax = a[0]
   
   0x0040160a <+70>:    mov    %eax,-0x8(%ebp)		//t = a[0]
   
   0x0040160d <+73>:    mov    -0x4(%ebp),%eax		//eax = 0
   0x00401610 <+76>:    lea    0x0(,%eax,4),%edx	//edx = 4 * 0
   0x00401617 <+83>:    mov    0x8(%ebp),%eax		//eax = *a
   0x0040161a <+86>:    add    %eax,%edx			//edx = *a + 4*0
   0x0040161c <+88>:    mov    -0x4(%ebp),%eax
   0x0040161f <+91>:    lea    0x0(,%eax,4),%ecx	
   0x00401626 <+98>:    mov    0xc(%ebp),%eax
   0x00401629 <+101>:   add    %ecx,%eax
   0x0040162b <+103>:   mov    (%eax),%eax
   0x0040162d <+105>:   mov    %eax,(%edx)			//a[0] = b[0]
   
   0x0040162f <+107>:   mov    -0x4(%ebp),%eax
   0x00401632 <+110>:   lea    0x0(,%eax,4),%edx	
   0x00401639 <+117>:   mov    0xc(%ebp),%eax
   0x0040163c <+120>:   add    %eax,%edx
   0x0040163e <+122>:   mov    -0x8(%ebp),%eax		//eax = t = a[0]
   0x00401641 <+125>:   mov    %eax,(%edx)			//b[0] = t;

   0x00401643 <+127>:   addl   $0x1,-0x4(%ebp)		//i+1
   0x00401647 <+131>:   mov    -0x4(%ebp),%eax
   0x0040164a <+134>:   cmp    0x10(%ebp),%eax 		//ebp + 16byte = arr_size cmp n
   0x0040164d <+137>:   jl     0x4015d3 <ControlTransfer+15>


2.条件数据传送

根据条件的成立来传输数据,不涉及分支操作。因为它的分支预测收益极低,所以指令流水线架构并没有为它提供分支预测的必要,以为一旦预测失败造成的损耗也是很大的。利润低风险大,没必要。

//条件数据传送
void DataTransfer(long a[],long b[],long n){
    for(long i = 0;i<n;i++) {
        long min = a[i] < b[i]?a[i]:b[i];
        long max = a[i] < b[i]?b[i]:a[i];
        a[i] = min;
        b[i] = max;
    }
}

得到的汇编代码如下所示,关键指令已标出。

   0x00401652 <+0>:     push   %ebp
   0x00401653 <+1>:     mov    %esp,%ebp
   0x00401655 <+3>:     sub    $0x10,%esp
   
   0x00401658 <+6>:     movl   $0x0,-0x4(%ebp)				
   0x0040165f <+13>:    jmp    0x4016e6 <DataTransfer+148>	//jmp到越界判断

   0x00401664 <+18>:    mov    -0x4(%ebp),%eax
   0x00401667 <+21>:    lea    0x0(,%eax,4),%edx
   0x0040166e <+28>:    mov    0xc(%ebp),%eax
   0x00401671 <+31>:    add    %edx,%eax			//eax =*a + 4 * 0 
   0x00401673 <+33>:    mov    (%eax),%eax			//eax = a[0]
   
   0x00401675 <+35>:    mov    -0x4(%ebp),%edx
   0x00401678 <+38>:    lea    0x0(,%edx,4),%ecx
   0x0040167f <+45>:    mov    0x8(%ebp),%edx
   0x00401682 <+48>:    add    %ecx,%edx			//edx =*b + 4 * 0
   0x00401684 <+50>:    mov    (%edx),%edx			//edx = b[0]

   0x00401686 <+52>:    cmp    %edx,%eax			//b[0] a[0]		<!--- 关键指令 ---!>		
   0x00401688 <+54>:    jle    0x40168c <DataTransfer+58> //b[0]<=a[0]  
   0x0040168a <+56>:    mov    %edx,%eax			//a[0] = b[0]
   
   0x0040168c <+58>:    mov    %eax,-0x8(%ebp) 		//-0x8(%ebp) = a[0] 
   
=> 0x0040168f <+61>:    mov    -0x4(%ebp),%eax		//eax = 0
   0x00401692 <+64>:    lea    0x0(,%eax,4),%edx 	//edx = 4*0
   0x00401699 <+71>:    mov    0x8(%ebp),%eax		//eax = *b
   0x0040169c <+74>:    add    %edx,%eax			//eax = *b + 4*0
   0x0040169e <+76>:    mov    (%eax),%eax			//eax = b[0]
   
   0x004016a0 <+78>:    mov    -0x4(%ebp),%edx		//edx = 0
   0x004016a3 <+81>:    lea    0x0(,%edx,4),%ecx	//ecx = 4*0
   0x004016aa <+88>:    mov    0xc(%ebp),%edx		//edx = *a
   0x004016ad <+91>:    add    %ecx,%edx
   0x004016af <+93>:    mov    (%edx),%edx			//edx = a[0]
   
   0x004016b1 <+95>:    cmp    %edx,%eax			//a[0] b[0]
   0x004016b3 <+97>:    jge    0x4016b7 <DataTransfer+101>  a[0]>=b[0]
   0x004016b5 <+99>:    mov    %edx,%eax			//b[0] = a[0]
   0x004016b7 <+101>:   mov    %eax,-0xc(%ebp)		//-0xc(%ebp) = b[0];
   
   0x004016ba <+104>:   mov    -0x4(%ebp),%eax		//eax = 0;	
   0x004016bd <+107>:   lea    0x0(,%eax,4),%edx	//edx = 4*0
   0x004016c4 <+114>:   mov    0x8(%ebp),%eax		//eax = *b
   0x004016c7 <+117>:   add    %eax,%edx			//*b+4*0
   0x004016c9 <+119>:   mov    -0x8(%ebp),%eax		//eax = -0x8(%ebp) => a[0]
   0x004016cc <+122>:   mov    %eax,(%edx)			//b[0] = -0x8(%ebp)
   
   0x004016ce <+124>:   mov    -0x4(%ebp),%eax
   0x004016d1 <+127>:   lea    0x0(,%eax,4),%edx
   0x004016d8 <+134>:   mov    0xc(%ebp),%eax		//eax = *a
   0x004016db <+137>:   add    %eax,%edx	
   0x004016dd <+139>:   mov    -0xc(%ebp),%eax
   0x004016e0 <+142>:   mov    %eax,(%edx)
   
   0x004016e2 <+144>:   addl   $0x1,-0x4(%ebp)    	//i + 1
				
   0x004016e6 <+148>:   mov    -0x4(%ebp),%eax	//循环越界判断
   0x004016e9 <+151>:   cmp    0x10(%ebp),%eax
   0x004016ec <+154>:   jl     0x401664 <DataTransfer+18>

3.相隔总时钟周期数的不同

通过汇编可以发现,两种方法都使用了cmp指令,但是你可以根据汇编发现他们在cmp后,jmp跳过的指令数量不同。对于PIPE-指令流水线来说,也就意味着时钟周期相隔的差异。
对于条件控制传送来说:
0x004015f7 <+51>: jle 0x401643 <ControlTransfer+127>
当cmp判断结束后,若是条件成立,设置了相应的条件码,那么jmp将会从 0x004015f7 -》0x401643。从中间隔的内存大小不难看出,相隔了数条指令,也就是总时钟周期跨度相对另一种方法很大。
对于条件数据传送来说:
0x00401688 <+54>: jle 0x40168c <DataTransfer+58> //b[0]<=a[0]
当cmp判断结束后,若是条件成立,设置了相应的条件码,那么jmp将会从 0x00401688 -》0x40168c ,仅仅相隔了一个指令(4byte),也就是一个时钟周期的延迟。那么当cmp指令位于执行阶段时,jle则处于取指阶段。

4.后言

综上,这就是为什么即使两种方法使用了同样的汇编指令,但性能的差异是截然不同的。总时钟周期数不同所导致的。条件控制传送,若是分支预测成功,它可以优化中间许多条指令,得到的CPE(Cycles Per Element 每个元素的周期数)效率将提升,但预测错误的风险也高,需要跨过数个周期回到原点来。而条件数据传送,jmp的目标地址和源地址仅仅一个周期数的差距,没有优化的必要性,他很稳定,不需要分支预测。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值