查看编译器生成的汇编代码,有助于我们分析程序的性能。
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条指令。无分支,避免了状态寄存器访问。