zoukankan      html  css  js  c++  java
  • 深入探讨用位掩码代替分支(2):汇编代码分析

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

    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条指令。无分支,避免了状态寄存器访问。

  • 相关阅读:
    JUnit入门
    Spring+Ibatis集成开发实例
    如花搞笑图片集锦(转贴)
    Jmeter接口測试
    java 解析国密SM2算法证书
    [BZOJ2324][ZJOI2011][最小费用最大流]营救皮卡丘
    定制ToolChain for ARM
    几种更新(Update语句)查询的方法
    linux杂谈(十七):iscsi存储分离技术
    17点50分系列-如何能让你的技术掌握的更深入?
  • 原文地址:https://www.cnblogs.com/zyl910/p/noifopex2.html
Copyright © 2011-2022 走看看