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

来源:互联网 发布:vb 表格控件 编辑:程序博客网 时间:2024/04/30 08:57
 

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

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");pushOFFSET FLAT:??_C@_0P@GACN@?$FLTest?3?5less0?$FN?6?$AA@ ; `string'call_printfaddesp, 4movesi, OFFSET FLAT:_bufmovedi, 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];movbx, WORD PTR [esi]; 52   : ; 53   : // 用if分支做饱和处理; 54   : m = n;moveax, ebx; 55   : if (m < 0) m = 0;cmpbx, bpmovDWORD PTR _m$[esp+28], eaxjgeSHORT $L53324movDWORD PTR _m$[esp+28], ebpmoveax, ebp$L53324:; 56   : by0 = (BYTE)m;; 57   : ; 58   : // 用位掩码做饱和处理.用求负生成掩码; 59   : by1 = (BYTE)(n & -(n >= 0));setgeclnegclandcl, bl; 60   : if (by1 != by0)printf("[Error] 1.1 neg: [%d] %d!=%d\n", n, by0, by1);// 验证cmpcl, almovBYTE PTR _by1$[esp+28], cljeSHORT $L53265movedx, DWORD PTR _by1$[esp+28]andeax, ediandedx, edipushedxpusheaxmovsxeax, bxpusheaxpushOFFSET FLAT:??_C@_0BO@OGNG@?$FLError?$FN?51?41?5neg?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'call_printfmoveax, DWORD PTR _m$[esp+44]addesp, 16; 00000010H$L53265:; 61   : ; 62   : // 用位掩码做饱和处理.用带符号右移生成掩码; 63   : by2 = (BYTE)(n & ~((signed short)n >> 15));movcx, bxsarcx, 15; 0000000fHnotclandcl, bl; 64   : if (by2 != by0)printf("[Error] 1.2 sar: [%d] %d!=%d\n", n, by0, by2);// 验证cmpcl, almovBYTE PTR _by2$[esp+28], cljeSHORT $L53260movecx, DWORD PTR _by2$[esp+28]andeax, ediandecx, edimovsxedx, bxpushecxpusheaxpushedxpushOFFSET FLAT:??_C@_0BO@EGHC@?$FLError?$FN?51?42?5sar?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'call_printfaddesp, 16; 00000010H$L53260:addesi, 2cmpesi, OFFSET FLAT:_buf+66048jlSHORT $L53259; 65   : }


 

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


2.1 用if分支做饱和处理

  C语言源码——

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

 

  汇编代码—— 

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

 

  核心指令共4条。


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

  C语言源码——

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


  汇编代码—— 

cmpbx, bp; *与0进行比较。...setgecl; *将大于等于标志赋给clnegcl; *将cl求负andcl, bl; *与原数值进行与运算


  核心指令共4条。


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

  C语言源码——

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


  汇编代码——

movcx, bx; 复制当前数据。不列入统计。sarcx, 15; *右移15位notcl; *对cl逐位取反andcl, bl; *与原数值进行与运算


  核心指令共3条。


3 分析“>255”处理

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

; 67   : // 检查 “>255”处理; 68   : printf("[Test: great255]\n");pushOFFSET FLAT:??_C@_0BC@LFG@?$FLTest?3?5great255?$FN?6?$AA@ ; `string'call_printfaddesp, 4movesi, 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];movbx, WORD PTR [esi]; 74   : ; 75   : // 用if分支做饱和处理; 76   : m = n;movecx, ebx; 77   : if (m > 255) m = 255;cmpbx, dimovDWORD PTR _m$[esp+28], ecxjleSHORT $L53275movDWORD PTR _m$[esp+28], edimovecx, edi$L53275:; 78   : by0 = (BYTE)m;; 79   : ; 80   : // 用位掩码做饱和处理.用求负生成掩码; 81   : by1 = (BYTE)(n | -(n >= 256) );cmpbx, 256; 00000100Hsetgealnegaloral, bl; 82   : if (by1 != by0)printf("[Error] 2.1 neg: [%d] %d!=%d\n", n, by0, by1);// 验证cmpal, clmovBYTE PTR _by1$[esp+28], aljeSHORT $L53278moveax, DWORD PTR _by1$[esp+28]andecx, ediandeax, edipusheaxpushecxmovsxecx, bxpushecxpushOFFSET FLAT:??_C@_0BO@FGBC@?$FLError?$FN?52?41?5neg?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'call_printfmovecx, DWORD PTR _m$[esp+44]addesp, 16; 00000010H$L53278:; 83   : ; 84   : // 用位掩码做饱和处理.用带符号右移生成掩码; 85   : by2 = (BYTE)(n | ((signed short)(255-n) >> 15));movax, disubax, bxsarax, 15; 0000000fHoral, bl; 86   : if (by2 != by0)printf("[Error] 2.2 sar: [%d] %d!=%d\n", n, by0, by2);// 验证cmpal, clmovBYTE PTR _by2$[esp+28], aljeSHORT $L53273movedx, DWORD PTR _by2$[esp+28]andecx, ediandedx, edimovsxeax, bxpushedxpushecxpusheaxpushOFFSET FLAT:??_C@_0BO@PGLG@?$FLError?$FN?52?42?5sar?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'call_printfaddesp, 16; 00000010H$L53273:addesi, 2cmpesi, OFFSET FLAT:_buf+131072jl$L53272; 87   : }


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


3.1 用if分支做饱和处理

  C语言源码——

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


  汇编代码——

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


   核心指令共4条。


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

  C语言源码——

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


  汇编代码——

cmpbx, 256; *与256进行比较。setgeal; *将大于等于标志赋给alnegal; *将al求负oral, bl; *与原数值进行或运算


  核心指令共4条。


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

  C语言源码——

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


  汇编代码——

movax, di; *复制255subax, bx; * ax = ax - bx = 255 - 当前数据sarax, 15; *右移15位oral, bl; *与原数值进行或运算


  核心指令共4条。


4 分析饱和处理

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

; 89   : // 检查 饱和处理; 90   : printf("[Test: saturation]\n");pushOFFSET FLAT:??_C@_0BE@BNPN@?$FLTest?3?5saturation?$FN?6?$AA@ ; `string'call_printfaddesp, 4movesi, 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];movbx, WORD PTR [esi]; 96   : ; 97   : // 用if分支做饱和处理; 98   : m = n;movecx, ebx; 99   : if (m < 0) m = 0;cmpbx, bpmovDWORD PTR _m$[esp+28], ecxjgeSHORT $L53288movDWORD PTR _m$[esp+28], ebp; 100  : else if (m > 255) m = 255;jmpSHORT $L53325$L53288:cmpbx, dijleSHORT $L53290movDWORD PTR _m$[esp+28], edi$L53325:movecx, DWORD PTR _m$[esp+28]$L53290:; 101  : by0 = (BYTE)m;; 102  : ; 103  : // 用位掩码做饱和处理.用求负生成掩码; 104  : by1 = LIMITSU_BYTE(n);cmpbx, bpsetgealnegalandal, blcmpbx, 256; 00000100Hsetgedlnegdloral, dl; 105  : if (by1 != by0)printf("[Error] 3.1 neg: [%d] %d!=%d\n", n, by0, by1);// 验证cmpal, clmovBYTE PTR _by1$[esp+28], aljeSHORT $L53293moveax, DWORD PTR _by1$[esp+28]andecx, ediandeax, edipusheaxpushecxmovsxecx, bxpushecxpushOFFSET FLAT:??_C@_0BO@MGFB@?$FLError?$FN?53?41?5neg?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'call_printfmovecx, DWORD PTR _m$[esp+44]addesp, 16; 00000010H$L53293:; 106  : ; 107  : // 用位掩码做饱和处理.用带符号右移生成掩码; 108  : by2 = LIMITSW_BYTE(n);movax, dimovdx, bxsubax, bxsarax, 15; 0000000fHsardx, 15; 0000000fHoral, blnotdlandal, dl; 109  : if (by2 != by0)printf("[Error] 3.2 sar: [%d] %d!=%d\n", n, by0, by2);// 验证cmpal, clmovBYTE PTR _by2$[esp+28], aljeSHORT $L53286moveax, DWORD PTR _by2$[esp+28]andecx, ediandeax, edipusheaxpushecxmovsxecx, bxpushecxpushOFFSET FLAT:??_C@_0BO@GGPF@?$FLError?$FN?53?42?5sar?3?5?$FL?$CFd?$FN?5?$CFd?$CB?$DN?$CFd?6?$AA@ ; `string'call_printfaddesp, 16; 00000010H$L53286:addesi, 2cmpesi, OFFSET FLAT:_buf+131072jl$L53285popedipopesipopebp; 110  : }


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


4.1 用if分支做饱和处理

  C语言源码——

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


  汇编代码——

xorebp, ebp; 在循环外将ebp设为0。不列入统计。movedi, 255; 在循环外将edi设为255。不列入统计。...movesi, OFFSET FLAT:_buf; 在循环外将esi指向buf。不列入统计。...movbx, WORD PTR [esi]; 加载数值。不列入统计。movecx, ebx; *复制数据到ecx。cmpbx, bp; *与0进行比较。movDWORD PTR _m$[esp+28], ecx; *存储数值。jgeSHORT $L53288; *若大于等于就跳转movDWORD PTR _m$[esp+28], ebp; *存储数值。jmpSHORT $L53325; *强制跳转$L53288:cmpbx, di; *与255进行比较。jleSHORT $L53290; *若小于等于就跳转movDWORD PTR _m$[esp+28], edi; *存储数值。$L53325:movecx, 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))) )


  汇编代码—— 

cmpbx, bp; *与0进行比较。setgeal; *将大于等于标志赋给alnegal; *将al求负andal, bl; *与原数值进行与运算cmpbx, 256; *与256进行比较。setgedl; *将大于等于标志赋给dlnegdl; *将dl求负oral, 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) )


  汇编代码——

movax, di; *复制255。movdx, bx; 复制当前数据。不列入统计。subax, bx; * ax = ax - bx = 255 - 当前数据sarax, 15; *右移15位sardx, 15; *右移15位oral, bl; *与原数值进行或运算notdl; *对dl逐位取反andal, dl; *与原数值进行与运算


  核心指令共7条。


5 小结

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

原创粉丝点击