zoukankan      html  css  js  c++  java
  • 深入探讨用位掩码代替分支(1):利用带符号移位生成掩码

      几年前我写了一篇“优化分支代码——避免跳转指令堵塞流水线”(http://blog.csdn.net/zyl910/article/details/1330614)。因当时是整理笔记,有些粗略。这几年又有了新的心得,故决定深入探讨,顺便回答网友评论。

      housisong(http://blog.csdn.net/housisong)提到了用利用带符号移位生成掩码——
    (假设n是32bit有符号数): (n>>31) 当n>=0的时候结果为0x00000000,当n<0时得到0xFFFFFFFF掩码,然后利用该掩码来合并分支。

      这是一个很好的思路,避免了状态寄存器访问。
      但该方案也有局限性——
    1.某些编程语言(如VB6)没有带符号移位运算符。
    2.仅能判断与0比较。但在很多时候,我们需要得到特定整数比较后的掩码。

      没有带符号移位运算符,问题不大。现在主流编程语言大多支持带符号移位。比如微软打造了VB.Net,支持带符号移位。
      计算特定整数比较后的掩码,最简单的就是前文所用的方法——根据“C语言比较运算的的结果是0和1”生成掩码。但现在已经知道该方法会访问状态寄存器,影响效率。有没有不依赖状态寄存器的办法呢?

      其中有一个思路就是利用减法,将“与特定整数的比较”转换为“与0的比较”。但这样做有时会产生溢出,导致运算结果不正确 或抛出异常(当检查整数溢出异常时)。如果再做溢出处理的话,增加了复杂度、影响效率。
      我曾因这个难题困扰很久。后来突然想到,在某些时候,其实并不需要处理溢出问题。

      因图像处理中最常用的带符号类型是16位整数,所以我在这里采用16位带符号整数(signed short),在计算掩码时应右移15位。


    一、计算掩码

    1.1 与0比较

      我们先热身一下,回顾一下与0比较时的掩码算法——
    MASK = n>>15 // “<0”时全1,“>=0”时全0

      加一个取反运算符后,可以得到这样的掩码——
    MASK = ~(n>>15) // “>=0”时全1,“<0”时全0

      如先对n求负,可以得到这样的掩码——
    MASK = (-n)>>15 // “>0”时全1,“<=0”时全0
    注意:会产生溢出。这是因为整数一般是采用补数表示法的,对于16位带符号数来说,值域为 [-32768, 32767],无法表示正数的32768。若忽略整数溢出异常,对-32768求负结果是-32768,进行带符号移位后会变为全1,与“>0”的设想不符。

      再加一个取反运算符后,可以得到这样的掩码——
    MASK = ~((-n)>>15) // “<=0”时全1,“>0”时全0
    注意:当n为-32768时会产生溢出。


    1.2 与X的比较

      上面的式子虽然是与0比较,但因式中没有写0,理解起来有点吃力。于是现在将0补上——
    MASK = (n-0)>>15 // “<0”时全1,“>=0”时全0
    MASK = ~((n-0)>>15) // “>=0”时全1,“<0”时全0
    MASK = (0-n)>>15 // “>0”时全1,“<=0”时全0
    MASK = ~((0-n)>>15) // “<=0”时全1,“>0”时全0

      观察上式,我们可以将0替换为任意整数X——
    MASK = (n-X)>>15 // “<X”时全1,“>=X”时全0
    MASK = ~((n-X)>>15) // “>=X”时全1,“<X”时全0
    MASK = (X-n)>>15 // “>X”时全1,“<=X”时全0
    MASK = ~((X-n)>>15) // “<=X”时全1,“>X”时全0

      因为现在是任意整数X,在进行减法运算时有可能产生溢出。


    二、饱和处理

      在编写图像处理程序时,经常出现RGB值超过[0, 255]范围的情况。这时,得做饱和处理,将越界数值饱和到边界,即这样的代码:
    if (r <   0) r =   0;
    if (r > 255) r = 255;

      现在我们将利用位运算,来优化这样的代码。

    2.1 “<0”时的处理

      我们可以利用与运算将数值修正为0,应选用【“>=0”时全1,“<0”时全0】的掩码。语句为——
    MASK = ~(n>>15) // “>=0”时全1,“<0”时全0
    m = n & MASK

      将其整理为一条表达式——
    m = n & ~(n>>15)

      因为该式没有用到减法,所以当n为任意值时都不会溢出。

    2.2 “>255”时的处理

      我们可以利用或运算,将超过范围的数值修正为全1。再将其与0xFF进行与运算,将超过范围的数值修正为255,这是根据255(0xFF)正好是低8位。

      怎么判断超过范围呢?有三种策略——
    A.“>255”。标准方式。
    B.“>=256”。因整数的连续性。
    C.“>=255”。因255进行饱和处理后,结果仍是255。

      因现在为了避免状态寄存器,不能利用比较语句,只能利用前面的位掩码算法。列出三种策略是为了找到效率最高的方案。
      回顾一下“1.2 与X的比较”,找出判断“>X”、“>=X”的式子——
    MASK = ~((n-X)>>15) // “>=X”时全1,“<X”时全0
    MASK = (X-n)>>15 // “>X”时全1,“<=X”时全0

      进过对比后发现,判断“>X”的运算量最少,所以我们应选择策略A。语句为——
    MASK = (255-n)>>15
    m = (n | MASK) & 0xFF

      将其整理为一条表达式——
    m = (n | ((255-n)>>15)) & 0xFF

      注意该式仅在n大于等于0时有效。

    2.3 饱和处理

      现在开始考虑实际的饱和处理,即将“<0”的修正为0,又将“>255”的修正为255。

      先整理一下上面的成果——
    m = n & ~(n>>15) // “<0”时的处理。n为任意值时都有效。
    m = (n | ((255-n)>>15)) & 0xFF // “>255”时的处理。仅在n大于等于0时有效。

      因“>255”处理在n小于0时无效,而“<0”处理在任何时候有效。所以我们可以先进行>255”处理,再进行“<0”处理,以屏蔽中间的错误值。语句为——
    m = ( (n | ((255-n)>>15)) & ~(n>>15) )  & 0xFF

      分析——
    当n<0时:虽然“(n | ((255-n)>>15))”的值无效,但因“~(n>>15)”的值为0,进行“& ~(n>>15)”运算后,结果为0。
    当n>=0且n<=255时:“((255-n)>>15)”的值为0,“| ((255-n)>>15)”会保留原值。“~(n>>15)”的值为全1,“& ~(n>>15)”也会保留原值。
    当n>255时:“((255-n)>>15)”的值为全1,“~(n>>15)”的值也为全1,最后遇到“& 0xFF”,结果为255。

      由于我们一般是将结果保存到一个BYTE型变量中,进行一次强制类型转换就行了,不需要“& 0xFF”——
    m = (BYTE)( (n | ((255-n)>>15)) & ~(n>>15) )


    三、实际运用

      在实际运用,上述代码比较长不易维护。可以将其封装为宏,并顺便推广一下——
    #define LIMITSW_FAST(n, bits) ( ( (n) | ((signed short)((1<<(bits)) - 1 - (n)) >> 15) ) & ~((signed short)(n) >> 15) )
    #define LIMITSW_SAFE(n, bits) ( (LIMITSW_FAST(n, bits)) & ((1<<(bits)) - 1) )

      bits代表限制多少位,如BYTE就是8——
    #define LIMITSW_BYTE(n) ((BYTE)(LIMITSW_FAST(n, 8)))


    四、测试代码

      测试代码如下——

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

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

    signed short buf[0x10000]; // 将数值放在数组中,避免编译器过度优化

    int main(int argc, char* argv[])
    {
    int i; // 循环变量(32位)
    signed short n; // 当前数值
    signed short m; // 临时变量
    BYTE by0; // 用if分支做饱和处理
    BYTE by1; // 用位掩码做饱和处理.用求负生成掩码
    BYTE by2; // 用位掩码做饱和处理.用带符号右移生成掩码

    //printf("Hello World!\n");
    printf("== noifCheck ==\n");

    // 初始化buf
    for(i=0; i<0x10000; ++i)
    {
    buf[i] = (signed short)(i - 0x8000);
    //printf("%d\n", buf[i]);
    }

    // 检查 “<0”处理
    printf("[Test: less0]\n");
    for(i=0; i<0x8100; ++i) // [-32768, 255]
    //for(i=0x7FFE; i<=0x8002; ++i) // [-2, 2]
    {
    // 加载数值
    n = buf[i];

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

    // 用位掩码做饱和处理.用求负生成掩码
    by1 = (BYTE)(n & -(n >= 0));
    if (by1 != by0) printf("[Error] 1.1 neg: [%d] %d!=%d\n", n, by0, by1); // 验证

    // 用位掩码做饱和处理.用带符号右移生成掩码
    by2 = (BYTE)(n & ~((signed short)n >> 15));
    if (by2 != by0) printf("[Error] 1.2 sar: [%d] %d!=%d\n", n, by0, by2); // 验证
    }

    // 检查 “>255”处理
    printf("[Test: great255]\n");
    for(i=0x8000; i<0x10000; ++i) // [0, 32767]
    //for(i=0x80FE; i<=0x8102; ++i) // [254, 258]
    {
    // 加载数值
    n = buf[i];

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

    // 用位掩码做饱和处理.用求负生成掩码
    by1 = (BYTE)(n | -(n >= 256) );
    if (by1 != by0) printf("[Error] 2.1 neg: [%d] %d!=%d\n", n, by0, by1); // 验证

    // 用位掩码做饱和处理.用带符号右移生成掩码
    by2 = (BYTE)(n | ((signed short)(255-n) >> 15));
    if (by2 != by0) printf("[Error] 2.2 sar: [%d] %d!=%d\n", n, by0, by2); // 验证
    }

    // 检查 饱和处理
    printf("[Test: saturation]\n");
    for(i=0; i<0x10000; ++i) // [-32768, 32767]
    //for(i=0x7FFE; i<=0x8102; ++i) // [-2, 258]
    {
    // 加载数值
    n = buf[i];

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

    // 用位掩码做饱和处理.用求负生成掩码
    by1 = LIMITSU_BYTE(n);
    if (by1 != by0) printf("[Error] 3.1 neg: [%d] %d!=%d\n", n, by0, by1); // 验证

    // 用位掩码做饱和处理.用求负生成掩码
    by2 = LIMITSW_BYTE(n);
    if (by2 != by0) printf("[Error] 3.2 sar: [%d] %d!=%d\n", n, by0, by2); // 验证
    }

    return 0;
    }

      测试结果——

    全部通过!

    源码下载——
    https://files.cnblogs.com/zyl910/noifCheck.rar

  • 相关阅读:
    == 和equals方法
    ObjectInputStream 与ObjectOutputStream
    IOS基础:ObjectiveC 数组处理
    学习笔记:自定义方法的两种实现方式
    DatePicker 获取时间的时区问题
    IOS基础:tableview中cell
    IOS基础:窗口切换的几种方法
    IOS基础:ObjectiveC 字符串处理
    使用 Notifications
    学习笔记:Tab Bar 控件使用详解
  • 原文地址:https://www.cnblogs.com/zyl910/p/noifopex1.html
Copyright © 2011-2022 走看看