深入探讨用位掩码代替分支(2):汇编代码分析

 

  查看编译器生成的汇编代码,有助于我们分析程序的性能。

1 让VC6输出编译的汇编代码

  用VC6打开前一篇文章(http://blog.csdn.net/zyl910/article/details/7345655)的工程“noifCheck.dsw”。

  首先需要配置项目设置——
1.点击菜单栏 “工程”->“Project Settings”打开“Project Settings”对话框。
2.将“Settings For:”设为“Win32 Release”。
3.将右侧的选项卡换到“C/C++”面板。
4.点击“Category:”组合框,选择“Listing Files”(列表文件)。
5.点击“Listing file type:”组合框,选择“Assembly with Source Code”(汇编与源码)。
6.点击“OK”保存设置。

  然后点击菜单栏 “编译”->“Batch Build...” 进行批生成——

  编译完成后,可在“Release”文件夹中找到“noifCheck.asm”,它就是编译器为“noifCheck.c”生成的汇编源码。


2 分析“<0”处理

  打开“noifVC6.asm”,找到“<0”处理的相关汇编代码——

; 45   : 	// 检查 “<0”处理
; 46   : 	printf("[Test: less0]\n");

	push	OFFSET FLAT:??_C@_0P@GACN@?$FLTest?3?5less0?$FN?6?$AA@ ; `string'
	call	_printf
	add	esp, 4
	mov	esi, OFFSET FLAT:_buf
	mov	edi, 255				; 000000ffH
$L53259:

; 47   : 	for(i=0; i<0x8100; ++i)	// [-32768, 255]
; 48   : 	//for(i=0x7FFE; i<=0x8002; ++i)	// [-2, 2]
; 49   : 	{
; 50   : 		// 加载数值
; 51   : 		n = buf[i];

	mov	bx, WORD PTR [esi]

; 52   : 
; 53   : 		// 用if分支做饱和处理
; 54   : 		m = n;

	mov	eax, ebx

; 55   : 		if (m < 0) m = 0;

	cmp	bx, bp
	mov	DWORD PTR _m$[esp+28], eax
	jge	SHORT $L53324
	mov	DWORD PTR _m$[esp+28], ebp
	mov	eax, ebp
$L53324:

; 56   : 		by0 = (BYTE)m;
; 57   : 
; 58   : 		// 用位掩码做饱和处理.用求负生成掩码
; 59   : 		by1 = (BYTE)(n & -(n >= 0));

	setge	cl
	neg	cl
	and	cl, bl

; 60   : 		if (by1 != by0)	printf("[Error] 1.1 neg: [%d] %d!=%d\n", n, by0, by1);	// 验证

	cmp	cl, al
	mov	BYTE PTR _by1$[esp+28], cl
	je	SHORT $L53265
	mov	edx, DWORD PTR _by1$[esp+28]
	and	eax, edi
	and	edx, edi
	push	edx
	push	eax
	movsx	eax, bx
	push	eax
	push	OFFSET FLAT:??_C@_0BO@OGNG@?$FLError?$FN?51?41?5neg?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'
	call	_printf
	mov	eax, DWORD PTR _m$[esp+44]
	add	esp, 16					; 00000010H
$L53265:

; 61   : 
; 62   : 		// 用位掩码做饱和处理.用带符号右移生成掩码
; 63   : 		by2 = (BYTE)(n & ~((signed short)n >> 15));

	mov	cx, bx
	sar	cx, 15					; 0000000fH
	not	cl
	and	cl, bl

; 64   : 		if (by2 != by0)	printf("[Error] 1.2 sar: [%d] %d!=%d\n", n, by0, by2);	// 验证

	cmp	cl, al
	mov	BYTE PTR _by2$[esp+28], cl
	je	SHORT $L53260
	mov	ecx, DWORD PTR _by2$[esp+28]
	and	eax, edi
	and	ecx, edi
	movsx	edx, bx
	push	ecx
	push	eax
	push	edx
	push	OFFSET FLAT:??_C@_0BO@EGHC@?$FLError?$FN?51?42?5sar?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'
	call	_printf
	add	esp, 16					; 00000010H
$L53260:
	add	esi, 2
	cmp	esi, OFFSET FLAT:_buf+66048
	jl	SHORT $L53259

; 65   : 	}


 

  下面对其进行整理和分析。


2.1 用if分支做饱和处理

  C语言源码——

// 用if分支做饱和处理
m = n;
if (m < 0) m = 0;
by0 = (BYTE)m;

 

  汇编代码—— 

	xor	ebp, ebp	; 在循环外将ebp设为0。不列入统计。
...
	mov	esi, OFFSET FLAT:_buf	; 在循环外将esi指向buf。不列入统计。
...
	mov	bx, WORD PTR [esi]	; 加载数值。不列入统计。
	mov	eax, ebx	; *复制数据到eax。
	cmp	bx, bp	; *与0进行比较。
	mov	DWORD PTR _m$[esp+28], eax	; 存储数值。不列入统计。
	jge	SHORT $L53324	; *若大于等于就跳转
	mov	DWORD PTR _m$[esp+28], ebp	; 存储数值。不列入统计。
	mov	eax, ebp	; *否则将eax设为0。
$L53324:

 

  核心指令共4条。


2.2 用位掩码做饱和处理.用求负生成掩码

  C语言源码——

// 用位掩码做饱和处理.用求负生成掩码
by1 = (BYTE)(n & -(n >= 0));


  汇编代码—— 

	cmp	bx, bp	; *与0进行比较。
...
	setge	cl	; *将大于等于标志赋给cl
	neg	cl	; *将cl求负
	and	cl, bl	; *与原数值进行与运算


  核心指令共4条。


2.3 用位掩码做饱和处理.用带符号右移生成掩码

  C语言源码——

// 用位掩码做饱和处理.用带符号右移生成掩码
by2 = (BYTE)(n & ~((signed short)n >> 15));


  汇编代码——

	mov	cx, bx	; 复制当前数据。不列入统计。
	sar	cx, 15	; *右移15位
	not	cl	; *对cl逐位取反
	and	cl, bl	; *与原数值进行与运算


  核心指令共3条。


3 分析“>255”处理

  找到“>255”处理的相关汇编代码——

; 67   : 	// 检查 “>255”处理
; 68   : 	printf("[Test: great255]\n");

	push	OFFSET FLAT:??_C@_0BC@LFG@?$FLTest?3?5great255?$FN?6?$AA@ ; `string'
	call	_printf
	add	esp, 4
	mov	esi, OFFSET FLAT:_buf+65536
$L53272:

; 69   : 	for(i=0x8000; i<0x10000; ++i)	// [0, 32767]
; 70   : 	//for(i=0x80FE; i<=0x8102; ++i)	// [254, 258]
; 71   : 	{
; 72   : 		// 加载数值
; 73   : 		n = buf[i];

	mov	bx, WORD PTR [esi]

; 74   : 
; 75   : 		// 用if分支做饱和处理
; 76   : 		m = n;

	mov	ecx, ebx

; 77   : 		if (m > 255) m = 255;

	cmp	bx, di
	mov	DWORD PTR _m$[esp+28], ecx
	jle	SHORT $L53275
	mov	DWORD PTR _m$[esp+28], edi
	mov	ecx, edi
$L53275:

; 78   : 		by0 = (BYTE)m;
; 79   : 
; 80   : 		// 用位掩码做饱和处理.用求负生成掩码
; 81   : 		by1 = (BYTE)(n | -(n >= 256) );

	cmp	bx, 256					; 00000100H
	setge	al
	neg	al
	or	al, bl

; 82   : 		if (by1 != by0)	printf("[Error] 2.1 neg: [%d] %d!=%d\n", n, by0, by1);	// 验证

	cmp	al, cl
	mov	BYTE PTR _by1$[esp+28], al
	je	SHORT $L53278
	mov	eax, DWORD PTR _by1$[esp+28]
	and	ecx, edi
	and	eax, edi
	push	eax
	push	ecx
	movsx	ecx, bx
	push	ecx
	push	OFFSET FLAT:??_C@_0BO@FGBC@?$FLError?$FN?52?41?5neg?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'
	call	_printf
	mov	ecx, DWORD PTR _m$[esp+44]
	add	esp, 16					; 00000010H
$L53278:

; 83   : 
; 84   : 		// 用位掩码做饱和处理.用带符号右移生成掩码
; 85   : 		by2 = (BYTE)(n | ((signed short)(255-n) >> 15));

	mov	ax, di
	sub	ax, bx
	sar	ax, 15					; 0000000fH
	or	al, bl

; 86   : 		if (by2 != by0)	printf("[Error] 2.2 sar: [%d] %d!=%d\n", n, by0, by2);	// 验证

	cmp	al, cl
	mov	BYTE PTR _by2$[esp+28], al
	je	SHORT $L53273
	mov	edx, DWORD PTR _by2$[esp+28]
	and	ecx, edi
	and	edx, edi
	movsx	eax, bx
	push	edx
	push	ecx
	push	eax
	push	OFFSET FLAT:??_C@_0BO@PGLG@?$FLError?$FN?52?42?5sar?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'
	call	_printf
	add	esp, 16					; 00000010H
$L53273:
	add	esi, 2
	cmp	esi, OFFSET FLAT:_buf+131072
	jl	$L53272

; 87   : 	}


  下面对其进行整理和分析。


3.1 用if分支做饱和处理

  C语言源码——

// 用if分支做饱和处理
m = n;
if (m > 255) m = 255;
by0 = (BYTE)m;


  汇编代码——

	mov	edi, 255	; 在循环外将edi设为255。不列入统计。
...
	mov	esi, OFFSET FLAT:_buf	; 在循环外将esi指向buf。不列入统计。
...
	mov	bx, WORD PTR [esi]	; 加载数值。不列入统计。
	mov	ecx, ebx	; *复制数据到ecx。
	cmp	bx, di	; *与255进行比较。
	mov	DWORD PTR _m$[esp+28], ecx	; 存储数值。不列入统计。
	jle	SHORT $L53275	; *若大于等于就跳转
	mov	DWORD PTR _m$[esp+28], edi	; 存储数值。不列入统计。
	mov	ecx, edi	; *否则将ecx设为0。
$L53275:


   核心指令共4条。


3.2 用位掩码做饱和处理.用求负生成掩码

  C语言源码——

// 用位掩码做饱和处理.用求负生成掩码
by1 = (BYTE)(n | -(n >= 256) );


  汇编代码——

	cmp	bx, 256	; *与256进行比较。
	setge	al	; *将大于等于标志赋给al
	neg	al	; *将al求负
	or	al, bl	; *与原数值进行或运算


  核心指令共4条。


3.3 用位掩码做饱和处理.用带符号右移生成掩码

  C语言源码——

// 用位掩码做饱和处理.用带符号右移生成掩码
by2 = (BYTE)(n | ((signed short)(255-n) >> 15));


  汇编代码——

	mov	ax, di	; *复制255
	sub	ax, bx	; * ax = ax - bx = 255 - 当前数据
	sar	ax, 15	; *右移15位
	or	al, bl	; *与原数值进行或运算


  核心指令共4条。


4 分析饱和处理

  找到饱和处理的相关汇编代码——

; 89   : 	// 检查 饱和处理
; 90   : 	printf("[Test: saturation]\n");

	push	OFFSET FLAT:??_C@_0BE@BNPN@?$FLTest?3?5saturation?$FN?6?$AA@ ; `string'
	call	_printf
	add	esp, 4
	mov	esi, OFFSET FLAT:_buf
$L53285:

; 91   : 	for(i=0; i<0x10000; ++i)	// [-32768, 32767]
; 92   : 	//for(i=0x7FFE; i<=0x8102; ++i)	// [-2, 258]
; 93   : 	{
; 94   : 		// 加载数值
; 95   : 		n = buf[i];

	mov	bx, WORD PTR [esi]

; 96   : 
; 97   : 		// 用if分支做饱和处理
; 98   : 		m = n;

	mov	ecx, ebx

; 99   : 		if (m < 0) m = 0;

	cmp	bx, bp
	mov	DWORD PTR _m$[esp+28], ecx
	jge	SHORT $L53288
	mov	DWORD PTR _m$[esp+28], ebp

; 100  : 		else if (m > 255) m = 255;

	jmp	SHORT $L53325
$L53288:
	cmp	bx, di
	jle	SHORT $L53290
	mov	DWORD PTR _m$[esp+28], edi
$L53325:
	mov	ecx, DWORD PTR _m$[esp+28]
$L53290:

; 101  : 		by0 = (BYTE)m;
; 102  : 
; 103  : 		// 用位掩码做饱和处理.用求负生成掩码
; 104  : 		by1 = LIMITSU_BYTE(n);

	cmp	bx, bp
	setge	al
	neg	al
	and	al, bl
	cmp	bx, 256					; 00000100H
	setge	dl
	neg	dl
	or	al, dl

; 105  : 		if (by1 != by0)	printf("[Error] 3.1 neg: [%d] %d!=%d\n", n, by0, by1);	// 验证

	cmp	al, cl
	mov	BYTE PTR _by1$[esp+28], al
	je	SHORT $L53293
	mov	eax, DWORD PTR _by1$[esp+28]
	and	ecx, edi
	and	eax, edi
	push	eax
	push	ecx
	movsx	ecx, bx
	push	ecx
	push	OFFSET FLAT:??_C@_0BO@MGFB@?$FLError?$FN?53?41?5neg?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'
	call	_printf
	mov	ecx, DWORD PTR _m$[esp+44]
	add	esp, 16					; 00000010H
$L53293:

; 106  : 
; 107  : 		// 用位掩码做饱和处理.用带符号右移生成掩码
; 108  : 		by2 = LIMITSW_BYTE(n);

	mov	ax, di
	mov	dx, bx
	sub	ax, bx
	sar	ax, 15					; 0000000fH
	sar	dx, 15					; 0000000fH
	or	al, bl
	not	dl
	and	al, dl

; 109  : 		if (by2 != by0)	printf("[Error] 3.2 sar: [%d] %d!=%d\n", n, by0, by2);	// 验证

	cmp	al, cl
	mov	BYTE PTR _by2$[esp+28], al
	je	SHORT $L53286
	mov	eax, DWORD PTR _by2$[esp+28]
	and	ecx, edi
	and	eax, edi
	push	eax
	push	ecx
	movsx	ecx, bx
	push	ecx
	push	OFFSET FLAT:??_C@_0BO@GGPF@?$FLError?$FN?53?42?5sar?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'
	call	_printf
	add	esp, 16					; 00000010H
$L53286:
	add	esi, 2
	cmp	esi, OFFSET FLAT:_buf+131072
	jl	$L53285
	pop	edi
	pop	esi
	pop	ebp

; 110  : 	}


  下面对其进行整理和分析。


4.1 用if分支做饱和处理

  C语言源码——

// 用if分支做饱和处理
m = n;
if (m < 0) m = 0;
else if (m > 255) m = 255;
by0 = (BYTE)m;


  汇编代码——

	xor	ebp, ebp	; 在循环外将ebp设为0。不列入统计。
	mov	edi, 255	; 在循环外将edi设为255。不列入统计。
...
	mov	esi, OFFSET FLAT:_buf	; 在循环外将esi指向buf。不列入统计。
...
	mov	bx, WORD PTR [esi]	; 加载数值。不列入统计。
	mov	ecx, ebx	; *复制数据到ecx。
	cmp	bx, bp	; *与0进行比较。
	mov	DWORD PTR _m$[esp+28], ecx	; *存储数值。
	jge	SHORT $L53288	; *若大于等于就跳转
	mov	DWORD PTR _m$[esp+28], ebp	; *存储数值。
	jmp	SHORT $L53325	; *强制跳转
$L53288:
	cmp	bx, di	; *与255进行比较。
	jle	SHORT $L53290	; *若小于等于就跳转
	mov	DWORD PTR _m$[esp+28], edi	; *存储数值。
$L53325:
	mov	ecx, DWORD PTR _m$[esp+28]	; *加载数值
$L53290:


  核心指令共10条。


4.2 用位掩码做饱和处理.用求负生成掩码

  C语言源码——

// 用位掩码做饱和处理.用求负生成掩码
by1 = LIMITSU_BYTE(n);
// #define LIMITSU_FAST(n, bits) ( (n) & -((n) >= 0) | -((n) >= (1<<(bits))) )


  汇编代码—— 

	cmp	bx, bp	; *与0进行比较。
	setge	al	; *将大于等于标志赋给al
	neg	al	; *将al求负
	and	al, bl	; *与原数值进行与运算
	cmp	bx, 256	; *与256进行比较。
	setge	dl	; *将大于等于标志赋给dl
	neg	dl	; *将dl求负
	or	al, dl	; *与原数值进行或运算


  核心指令共8条。


4.3 用位掩码做饱和处理.用带符号右移生成掩码

  C语言源码——

// 用位掩码做饱和处理.用带符号右移生成掩码
by2 = LIMITSW_BYTE(n);
// #define LIMITSW_FAST(n, bits) ( ( (n) | ((signed short)((1<<(bits)) - 1 - (n)) >> 15) ) & ~((signed short)(n) >> 15) )


  汇编代码——

	mov	ax, di	; *复制255。
	mov	dx, bx	; 复制当前数据。不列入统计。
	sub	ax, bx	; * ax = ax - bx = 255 - 当前数据
	sar	ax, 15	; *右移15位
	sar	dx, 15	; *右移15位
	or	al, bl	; *与原数值进行或运算
	not	dl	; *对dl逐位取反
	and	al, dl	; *与原数值进行与运算


  核心指令共7条。


5 小结

  在做饱和处理时——
1、if分支法:10条指令。不仅用到了多条跳转指令,还需要将变量暂存在比寄存器慢很多的内存中。
2、求负生成掩码法:8条指令。无分支,但仍需访问状态寄存器。
3、移位生成掩码法:7条指令。无分支,避免了状态寄存器访问。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值