现在的位置: 首页 > 综合 > 正文

怎样快速判断掩码第一个为1的Bit位置

2013年12月11日 ⁄ 综合 ⁄ 共 1949字 ⁄ 字号 评论关闭
在底层软件开发过程中经常使用位掩码标识一个状态符号。举一个例子来说,比如一个U32类型的变量Use_Mask用来表示32个内存块的占用状态,变量的每一位代表一个内存块的使用状态,1b表示空闲,0b表示被占用。当应用程序需要使用一个空闲块时,只需要查询Use_Mask哪一位为1,就可以直接将给Bit位对应的内存块拿来使用了,当然在使用前将该位置1了。同理,使用完给内存块后,也需要将对应位置0就可以了。
那么,如何根据Use_Mask得到为1位的位置呢,比较直接的方法如下:
int GetUnUseMem(U32 UseMask)
{
    int i;
    U32 temp=UseMask;
    
    for(i=0;i<32;i++)
    {
        if(temp &0x00000001)
            return i;
        temp= temp>>1;
    }
    return -1;
}
显然,用这种方法可以得到期望的结果,但是该函数的计算量是与输入参数的内容相关的,最坏情况的运算时间可能是最好情况的32倍。这在一些实时要求比较高的系统中是很难接受的。同时,在函数代码中存在循环,判断分支,也非常不适合当前具有预取指令,乱序执行等功能的处理器提高其性能。当然,我们可以将循环展开,定义寄存器变量等技巧来解决一部分问题,但是是否有其它方法吗。这里考虑了两种方案,抛砖引玉:

1、查找表

该方法一般在掩码长度为8位时比较常见,即定义一个数组X,数组长度为2 的n次方,n为掩码的长度。如掩码为8位时可定义一个256个单元的数组,数组第m个单元初始化为但掩码值为m时其第一个为1位的位置,比如m=0xF4,则11110100b,其第一个为一的位在bit2上,则X[m]=2,同理但X[0xF5]=0,当然X[0x00]=-1。有了这张表,那么查找比特1位置的函数实现很简单:
int X[256]={-1,0,1,0,...}
int GetUnUseMem(U8 UseMask)

{
    return X[UseMask];
}
那么问题是当UseMask为32位时怎么办,难道构建2的32次方长度的数组吗,也即4G个单元,当然不好,除非你要想搞死你的机器。那就分段处理吧。
int GetUnUseMem(U32 UseMask)
{
    int    Ret;
    if((
Ret= X[UseMask&0xFF])>=0) goto GetUnUseMem_End;
    if((Ret= (X[UseMask&0xFF00 >>8] +8))>=0) goto GetUnUseMem_End;
    if((Ret= (X[UseMask&0xFF0000 >>16] +16))>=0) goto GetUnUseMem_End;
    Ret= X[UseMask&0xFF000000 >>24] +24;
GetUnUseMem_End:
    return Ret;
}
如果是64位的掩码呢,最多要判断8次,显然不合适,因此可以使用两级解码,第一级掩码中一位代表下一级掩码的相应8位是否有1。
int X[256]={-1,0,1,0,...}
U8
Y[256]={0,0,8,0,16, 0 ,...}
int GetUnUseMem(U8 UseMask1,U64 UseMask)
{
    register U8 index =
Y[UseMask1];
    index =
(U8)(UseMask2>>index & 0xFF);
    return X[index ];
}

2、位运算

使用位运算可以更快的得到,也不需要用查找表占用内存空间,由于访问内存非常影响处理器的效率,而使用这种方式会使用寄存器或者CPU缓存,不通过北桥再经过DDRII内存控制器得到,因此效率比查找表要高。
int GetUnUseMem(U32 UseMask)
{
    register U32    index =
UseMask ;
    //将第一个为1位的低位都置1,其它位都置0
    index = (index-1)  &  ~index;
    //得到有多少为1的位
    index = index&0x55555555 + (index>>1)&0x55555555; 
    index = index&0x33333333+
(index>>2)&0x33333333;
    index = index&0x0F0F0F0F+ (index>>4)&0x0F0F0F0F;
    index = index&0xFF + (index&0xFF00 >> 8) + (index&0xFF0000 >> 16) + (index&0xFF000000 >> 24);
    //得到位数,如果为32则表示全0
    return (int)(index);
}
 

抱歉!评论已关闭.