#內核裏的神函數# 之 hweight32

    在閱讀iptables內核模塊的代碼時,我遇到了這樣一個函數hweight32:html

unsigned int hweight32(unsigned int w)
{
    unsigned int res = w - ((w >> 1) & 0x55555555);
    res = (res & 0x33333333) + ((res >> 2) & 0x33333333);
    res = (res + (res >> 4)) & 0x0F0F0F0F;
    res = res + (res >> 8);
    return (res + (res >> 16)) & 0x000000FF;
}

    乍看起來,彷佛很難理解這段代碼的功能,其實它就完成了「統計給定數字中值爲1的bit位個數」的功能,怎麼樣,是否是有點風馬牛不相及的感受。linux

    下面咱們先看網絡上的一篇分析文章網絡

============轉載:華麗的分割線============
函數

在 linux 內核裏,在計算 CPU 個數時用到了 hweight32 這個函數,這個函數就是漢明重量的計算方法。對於二進制串來講,它就是計算這個二進制串裏含有多少個 1 。hweight32() 函數實現以下:.net

/*代碼如上*/

下面先對一個任意的二進制串中 1 的數量的計算推出上面的公式:
1. 假設有 1 個字串爲:0110 1100 1011 1010,姑且將這個字串簡稱爲 A
2. 咱們先將 A 以每 2 位一組進行分組,即 01 10 11 00 10 11 10 10 ,而後計算出每組含有 1 的個數,方法以下:

<1> 首先用 0101 0101 0101 0101 即 (0x5555) 來和 A 相與:
0110 1100 1011 1010
0101 0101 0101 0101 &
---------------------------------
0100 0100 0001 0000                    (將這個結果設爲 B)

通過上面的計算,實際上咱們已經得出了 A 中每小組中(每 2 位一組)的偶數位的 1 的個數,即(1,0,1,0,0,1,0,0)。

<2> 在步驟 <1> 中咱們算出了每組中偶數位 1 的個數,接下來要算出奇數位中 1 的個數,方法是先將 A 右移 1 位 (A >> 1),而後與 0x5555 再相與。A 右移 1 位就是將原來的奇數位變爲偶數位:
0011 0110 0101 1101      (A>>1)
0101 0101 0101 0101      &
---------------------------------------------
0001 0100 0101 0101                    (將這個結果設爲 C)

<3> 如今,將 B 和 C 相加起來,就能獲得 A 中每 2 位爲一組的每組中的 1 的個數:
D = B + C
D =  0100 0100 0001 0000  + 0001 0100 0101 0101 = 01 01 10 00 01 10 01 01
                                                                                      (1   1   2   0   1   2   1   1)

好,如今咱們已經算出了 A 中以每 2 位分紅一組的每一個小組中 1 的個數。同理,接下來咱們要利用上面的結果(D)算出 A 中以每 4 位分紅一組中每一個小組中 1 的個數:

<4> 用 D 和 0011 0011 0011 0011 (0x3333)進行相與:
0101 1000 0110 0101
0011 0011 0011 0011  &
---------------------------------
0001 0000 0010 0001                (將這個結果設爲 E)

<5> 再將 D 右移 2 位,即 D >> 2 後,再和 0x3333 相與:
0001 0110 0001 1001                 (D>>2)
0011 0011 0011 0011      &
------------------------------------
0001 0010 0001 0001                (將這個結果設爲 F)

<6> 將 E 和 F 相加:
G = E + F
G = 0001 0000 0010 0001 + 0001 0010 0001 0001 = 0010 0010 0011 0010
                                                                                     (2      2       3       2     )
也就是說,A 中以每 4 位分紅一組後,每小組中 1 的個數分別爲 2, 2, 3, 2

<7> 一樣的方法,下面再計算每 8 位一組的每小組中的 1 的個數 (逐步逼近)

先將 G 和 0x0F0F 相與:
0010 0010 0011 0010
0000 1111 0000 1111  &
--------------------------------
0000 0010 0000 0010               (將這個結果設爲 H)

同理再將 G 右移 4 位後與 0x0F0F 相與:
0000 0010 0010 0011
0000 1111 0000 1111   &
---------------------------------
0000 0010 0000 0011                (將這個結果設爲 I)

<8> H 和 I 相加後即得每 4 位分組後每小組 1 的個數:
J = H + I
J = 0000 0010 0000 0010  + 0000 0010 0000 0011 = 0000 0100 0000 0101
                                                                                    (          4            5        )

<9> 到此,之差最後逼近一步,即可獲得咱們想要的結果,如今將 J  與 0x00FF 相與:
0000 0100 0000 0101
0000 0000 1111 1111   &
--------------------------------
0000 0000 0000 0101          (將這個結果設爲 K)

<10> 將 J 右移 8 位後再與 0x00FF 相與:
0000 0000 0000 0100
0000 0000 1111 1111  &
---------------------------------
0000 0000 0000 0100           (將這個結果設爲 L)

<11> 將 K 和 L 相加:
M = K + L = 0000 0000 0000 0101 + 0000 0000 0000 0100 = 5 + 4 = 9

最終結果爲 9 ,這就是字串 A 中的 1 的個數。
code

============轉載: 華麗的分割線============htm

    下面再來簡單的逐行分析一下:ip

unsigned int res = w - ((w >> 1) & 0x55555555);

    將w按2bit爲一組劃分,統計每組中1的個數;get

    這裏並無作兩次「位與&「的操做,其中蘊含的一個小技巧是:對於一個2bit的數字,其值減去它右移一位的結果就是這個2bit數字中1的個數,例如:it

    11 - 01 = 10 (2):11中含有的1的個數爲2;

    10 - 01 = 01 (1):10中含有的1的個數爲1;

    01 - 00 = 01 (1):01中含有的1的個數爲1;

    00 - 00 = 00 (0):00中含有的1的個數爲0;

res = (res & 0x33333333) + ((res >> 2) & 0x33333333);

    將第一步的計算結果以每4位爲一組,計算每組中1的個數;

res = (res + (res >> 4)) & 0x0F0F0F0F;

    將上一步的計算結果以每8位爲一組,計算每組中1的個數;

    因爲上一步中計算的是每4位爲一組的結果,所以其最大值爲4(0100),所以能夠直接相加而不用擔憂溢出,並將高四位歸0;

res = res + (res >> 8);

    將上一步的計算結果以每16位爲一組,計算每組中1的個數;

return (res + (res >> 16)) & 0x000000FF;

    將高16bit中1的個數與低16bit中1的個數相加,就獲得了最後的結果;


結論:請收下個人膝蓋!

相關文章
相關標籤/搜索