继续看微软的bit位算法,特殊位赋值取值

功能:把一个数值通过严格的规定,放入到内存的某一个或者几个bit位上。后续如果要使用的话,再取出来。这么做的目的,节约内存,提高性能。

老规矩,直接上代码和注释:.

void EEJitManager::NibbleMapSetUnlocked(HeapList * pHp, TADDR pCode, BOOL bSet){    size_t delta = pCode - pHp->mapBase; //mapBase某段内存的起始地址,pCode某个函数的函数头地址或者    某个函数的中间的某个地址。delta就是当前pCode距离起始内存mpBase的偏移        size_t pos  = ADDR2POS(delta);//把偏移delta右移5bit位:delta>>5     DWORD value = bSet?ADDR2OFFS(delta):0;//把delta从右第一位开始往左数5位也就是&31,右移2,加1:delta&31>>2+1    DWORD index = (DWORD) (pos >> LOG2_NIBBLES_PER_DWORD);//把pos变量再次右移3位       DWORD mask  = ~((DWORD) HIGHEST_NIBBLE_MASK >> ((pos & NIBBLES_PER_DWORD_MASK) << LOG2_NIBBLE_SIZE));//这段代码是精华所在:(0xf<<28)>>(pos&7<<2),pos&7最大值只能是7,左移2位,得到的值把(0xf<<28)右移,看下这个(0xf<<28)它的二进制是:1111 0000 0000 0000 0000 0000 0000 0000。假如pos等于1,则(0xf<<28)向右移4位,得到:0000 1111 0000 0000 0000 0000 0000 0000。注意它这个前面还有个取反的符号(~),所以把:0000 1111 0000 0000 0000 0000 0000 0000取反,则得到:1111 0000 1111 1111 1111 1111 1111 1111。注意看这个得到二进制从左往右第五位到第八位全部为零,其余位1。这是因为这第五到第八位要进行赋值,把这几位清空为零,其余的保持不变。它的操作在下面,代码是通过&的算法来:*(pMap+index))&mask。
    value = value << POS2SHIFTCOUNT(pos);//上面把二进制的第五到第八位清空,那么这几个bit位的要赋的值就是这里的value。看下value的算法,POS2SHIFTCOUNT(pos)==28-pos&7<<2。    这个pos假设等于1,那么28-pos&7<<2则等于24。把value左移24个bit位,实质上就是类似于这种(假设value等于1):0000 0001 0000 0000 0000 0000 0000 0000。注意这里的二进制是四字节,    看下这里的二进制的第五位到第八位,是0001,跟上面的mask的第五位和第八位吻合上了。下面要做的就是给后者赋值到前者。       PTR_DWORD pMap = pHp->pHdrMap;    _ASSERTE(!value || !((*(pMap+index))& ~mask));Synchronization would be required with FindMethodCode otherwise.    *(pMap+index) = ((*(pMap+index))&mask)|value; //这里就是赋值了。先&上mask,因为mask的第五到第八位是0其余为1,    也就是把*(pMap+index)取到的值的第五位到第八位置为0,其它位不变。然后|value,也就是把value值放入到第五到第八位的位置。最后赋给了*(pMap+index)。}

它这段代码最妙的地方在于,它能够计算出它需要存放的bit位的位置,然后把数值放入到相应的bit位的位置。

下面看下取值:

TADDR EEJitManager::FindMethodCode(RangeSection * pRangeSection, PCODE currentPC){    HeapList *pHp = pRangeSection->_pHeapList;
    TADDR base = pHp->mapBase;//内存的起始地址    TADDR delta = currentPC - base; //currentPC是当前函数里面的某个地址,可能是函数头,    //也可能是函数中间的值,获取currentPC距离内存其实值base的偏移delta        PTR_DWORD pMap = pHp->pHdrMap;    PTR_DWORD pMapStart = pMap;
    DWORD tmp;    size_t startPos = ADDR2POS(delta); //这里同赋值一样,右移5位                                      DWORD  offset   = ADDR2OFFS(delta);//这里同赋值一样,delta&31>>2+1    pMap += (startPos >> LOG2_NIBBLES_PER_DWORD); // 这里继续把pos右移3位,相当于上面赋值index变量,    //然后加上pMap也就是内存的地址        tmp = VolatileLoadWithoutBarrier<DWORD>(pMap) >> POS2SHIFTCOUNT(startPos);//这里POS2SHIFTCOUNT(startPos)是    // 28-startPos&7<<2,因为赋值是把POS2SHIFTCOUNT(startPos)左移,所以    //这里右移是还原到真正的值
    if ((tmp & NIBBLE_MASK) && ((tmp & NIBBLE_MASK) <= offset) )    {        return base + POSOFF2ADDR(startPos, tmp & NIBBLE_MASK);//这里就是把内存起始地址加上POSOFF2ADDR        //而POSOFF2ADDR==startPos<< 5 + (tmp&NIBBLE_MASk -1) << 2,也就是把真正值还原出来。    }

取值不过是赋值的反向操作,比赋值要简单许多。

以上代码的精华主要在于下面这一段,神来之笔。通过位移找到需要赋值的位操作,后续模(&)上实际赋值的地方,进行某一个或者几个位的清空操作:

DWORD mask  = ~((DWORD) HIGHEST_NIBBLE_MASK >> ((pos & NIBBLES_PER_DWORD_MASK) << LOG2_NIBBLE_SIZE));

总结下,这里的赋值和取值好像是多余的操作,多此一举。因为currentPC或者pCode里面包含的就是要返回的值,其实不然,因为currentPC或者pCode或许是某个函数的中间的某个地址,而这里需要返回的是函数头的地址(函数的起始地址),所以以上取值赋值的操作确保返回的值是函数头地址,而非其它地址。