zyl910

优化技巧、硬件体系、图像处理、图形学、游戏编程、国际化与文本信息处理。

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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

 

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

  用VC6打开前一篇文章(http://www.cnblogs.com/zyl910/archive/2012/03/12/noifopex1.html)的工程“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条指令。无分支,避免了状态寄存器访问。

posted on 2012-03-21 15:10  zyl910  阅读(967)  评论(0编辑  收藏  举报