contributed by <marvin0102>
假設我們要求 的平方根,將 轉為二進位後,可以將 拆成 2 的冪相加:
其中 為 bits pattern,而 為最高位的 1 。
設 、 … ,因此可以將 拆成:
, or
使用矩陣乘法可得
可以將這個矩陣拆為:
至最後一向
將這些矩陣結合可以整理成以下公式
其中
而所求 即為所求。
可以知道
經由測試 判斷 or
時, 即為所求
可以透過上一輪的差值計算比較 ,設 為 、 的差值,可以由上一輪的差值 減去 而得, 的計算如下:
因此,可以由上一輪的 計算
將 拆成 ,可得
其中 、
接著嘗試通過 、 推論下一輪的 、 :
接下來就可以游上往下尋找 ,設從 開始往下尋找:
因為 最小為 1 ,但是座位移操作最後必為 0 ,因此我們可以將 作為搜尋的判斷式。
從原始碼來解析:
因為無法計算虛數,因此須先將 為負數的情況排除。
從 1UL << ((31 - __builtin_clz(x)) & ~1UL)
的作用為計算 中最大 2 的冪為多少,也就是 中 ,因此我們可以知道 m
為 。
又由上面的公式, 可知,每一輪迴圈 m
須除 4 ,也就是右移 2 bits ,因此 AAAA
為 2
。
從 return z;
及 z
的起始值為 0 可推得, z
即為 ,且 b
為 ,又因為 z
每次累加 ,因此需要每次迴圈除 2 ,使 z
變為 ,因此 BBBB
為 1。
在 block/blk-wbt.c 中尋找到函式 rwb_arm_timer
使用 int_sqrt
的應用案例。
int_sqrt
被定義於 lib/math/int_sqrt.c
中,用於計算整數的平方根。
從註解可以了解
如果上述 window
中的最小延遲超過某個目標值,則增加 scaling step
,並將佇列深度縮小 factor 的 2 倍。然後,monitoring window
縮小為 100 / sqrt(縮放步驟 + 1)。
具體程式碼:
TODO
不使用 /
與 %
求一數字除以 10 的商跟餘數,相當於乘 ,但因為 10 不能以 2 的冪表示,因此需要找近似值 使近似於 ,當 、 時, 與 10 很接近
因為 因此我們可以透過 (((tmp >> 3) + (tmp >> 1) + tmp) << 3)
計算 ,因為右移時,會遺失最低位的 3 個位元,因此須利用 + d0 + d1 + d2
,紀錄並將其加回。
在我們得到 後,在除以 128 即為我們所求的商,也就是 >> 7
。
而餘數可以利用公式, 得到,r = tmp - (((q << 2) + q) << 1);
。
完整程式碼為:
可包裝為以下函式:
起初在觀察程式時,並沒有理解到兩程式之間的關聯性,在閱讀 Hacker's Delight 時,發現兩程式都是用近似值計算除以 10 的結果。
x = (in | 1) - (in >> 2)
計算 ,q = (x >> 4) + x
可以得 近似 0.8,有此可知 *div = (q >> CCCC)
須將 q
除以 8 才可得 in/10
,因此 CCCC
為 3
。
而四次位移是為了縮小誤差。
而餘數的計算同樣是將 in - div*10
而得,(q & ~0x7)
保留除了最後三位元以外的其他位元,能等同於 div<<3
,因為 10 = 0b1010
,我們已經取得了div<<3
,引此 (*div << DDDD)
為 (*div << 1)
。
當我們計算以 2 為第底數的對數時,我們可以計算一數的 leading set bit (從 most significant bit 開始計算)得到對數的近似值,例如:
我們可以使用類似二元搜尋樹的方式加快 leading set bit 的搜索,方法如下:
從第一個迴圈中i >>= 16
可知,此迴圈一次檢查 16 位元,如果 i
的 leading set bit 大於 16 位時,可以直接將 i 右移 16 位元,因此 AAAA
為 0xffff
。
後面的迴圈同樣分別檢查 8 、 4 、 1 位元,因此 BBBB
、 CCCC
分別為 0xff
與 0xf
。
可以利用 GNU extension __builtin_clz
來改寫使得程式更精簡:
從 GCC online documentation 的敘述:
Built-in Function: int __builtin_clz (unsigned int x)
Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined
可以知道 __builtin_clz
返回從 most significant bit (32 or 64) 開始計算,到碰的第一個 set bit 為止, 0 的數量。
其中,前綴的 __builtin_
代表此函式為 GCC 的 built-in 函式,
因此,ilog32(n)
就是先使用 __builtin_clz(n)
計算 leading bits 的數量,再用 31 減掉,就可以得到從 most significant bit 數來第一個 set bit 的位置。
在 include/linux/log2.h 中定義了 log2 相關的聚集 ilog
,其中包含了函式 __ilog2_u64
及 __ilog2_u32
,使ilog2
可以計算變數大小為 64 或 32 位元的對數值。
Linux/block 負責磁碟管理,其中block/blk-zoned.c 使用 ilog2
計算 zone 的個數。
從函式 ewma_init
中, avg->factor
為 factor
的對數,且函式 ewma_read
中,返回值為 avg->internal >> avg->factor
可以知道,再做 ewma 計算時,會以 做計算,當讀取數值時,須先將 才會得到正確的值。
在此前提下,分析函式 ewma_add
:
對照 EWMA 的數學定義:
當加入第一筆資料時 對應到 avg->internal = (val << avg->factor)
符合觀察。因此, 在程式內乘上 後,
變為 ,但在函式中卻用了
>> avg->weight
,在觀察程式後發現,將 帶入公式計算可以發現,原本的公式變為 ,
同乘 後 與 ((avg->internal << avg->weight) - avg->internal) + (val << avg->factor)
相符,因此最後計算的結果須除 才會得到正確的結果。
include/linux/average.h 定義了 EWMA 的巨集 DECLARE_EWMA
,其中可以使用巨集引數 name
自行定義 EWMA 相關函式名稱,分別有 ewma_##name##_init
、 ewma_##name##_read
、 ewma_##name##_add
三個函式與結構體 ewma_##name
。
DECLARE_EWMA
引數中 _precision
為定點數得 fraction bit 個數,而 EWMA 數學定義中的 則是以 1/_weight_rcp
的形式定義。
在 drivers/net/wireless/ath/ath11k/core.h 中,使用了 DECLARE_EWMA(avg_rssi, 10, 8)
定義了結構體 ewma_avg_rssi
,並且用 ath11k_sta
包裝。
在 drivers/net/wireless/ath/ath11k/mac.c 中函式 ath11k_mac_station_add
初始化,確切的作用仍在理解中。
函式 ceil_ilog2
的作用為計算以2為底數的對數並且向上取整,實作方法與測驗 3 類似,通過二元搜尋的方式找出從 msb開始計算的 leading set bit 的位置。
r
為記錄leading set bit 的位置,當 x > 0xFFFF
成立時, r
為 ,代表 leading set bit 大於 16 位元,而 x >>= r
則是將 x
右移 16 位元再做後續的比較,若 x > 0xFFFF
不成立時,則 r = 0
且 x
無位移。
在檢驗完 16 位元後,接續檢驗 8 位元,而 r |= shift
則是將兩次檢驗的位元結果相加。
再分別檢驗完 16 、 8 、 4 、 2 位元後,接著將這些檢驗結果與 (x>1)
相加即為從 msb開始計算的 leading set bit 的位置。
而因為結果需要向上取整,因此最後的結果需要加 1 ,如果 x
為 則結果就會錯誤,因此在函示一開始需加上 x--
以避免此情況。
在原本的函示中,因為 x--
在 x
為 0 時,會將 x
變為負數進而無法計算,因此只要將程式碼改為:
因為需要計算陣列 nums 中任兩個元素的 Hamming distance ,因此需要兩個迴圈將陣列 nums 中的元素相互比對並且透過 __builtin_popcount(nums[i] ^ nums[j]
計算 Hamming distance ,但因為兩層迴圈都是從頭開始走訪陣列 nums 因此會有重複計算的問題,例如:當 i=0 時, nums[i] ^ nums[j]
會計算 j=1,2,…,n, 而 i=1
時,會計算 j=0,2,…,n , 其中 nums[0] ^ nums[1]
nums[1] ^ nums[0]
重複計算了,因此當計算完成時, total
為兩倍的 Hamming distance ,在最後還須除 2 ,也就是 total>>1
。
觀察每個 bit 的 Hamming distance :
n'th bit | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|
input 7 | 0 | 0 | 1 | 1 | 1 |
input 5 | 0 | 0 | 1 | 0 | 1 |
input 10 | 0 | 1 | 0 | 1 | 0 |
input 17 | 1 | 0 | 0 | 0 | 1 |
每個 bit 的 Hamming distance 為陣列中取兩個元素,滿足兩元素中該 bit 為 [0,1] 或 [1,0] 的組合數量。例如:
0
-> Hamming Distance = (1)*(numsize - 1)0
-> Hamming Distance = (2)*(numsize - 2)因此,我們可以統整所有元素中每個 bit 為 1 的數量,並計算每個 bit 的 Hamming distance ,最後再加總起來就是 total Hamming distance。
程式碼如下:
觀察陣列 move_masks
,因為井字遊戲最多只有 9 個可能的選擇,因此可以知道 move_masks
中每一個元素代表井字遊戲的其中一個位置。將九宮格內的直線、橫線與斜線個數相加剛好為 8 條連線,又每條連線為九宮格中的三格連線所形成。
觀察元素內的數值可以發現,相比於直接將座標進行編號, move_masks
的做法是將九宮格中的連線做編號,每 4 個 bits 紀錄一條連線,則 uint32_t
恰好可以記錄 8 條連線。
在紀錄著一條連線的 4 bits 中,每位元代表連線中的其中一個格子,當遊戲的其中一方勝利時,代表 8 條連線中,有至少一條連線的三個格子被遊戲中的一方填滿,此時紀錄著這條連線的 4 bits 為 0111
即 0x7
。
這時只要將記錄著遊戲狀態的變數 player_board
加 0x11111111
即每 4 bits 加 1 ,並觀察是否有其中 4 bits 進位為 1000
即 0x8
就可知道是否達成勝利條件。
井字遊戲中,每一個格在都會影響到不只一條連線,因此 move_masks
的每個元素 set bit 個數至少為三以上,且每 4 bits 只會有一個 set bit。
觀察遊戲的整體流程, boards
負責記錄玩家的遊戲狀態,available_moves
代表目前可以下棋的位置,一開始皆為 9 個,而決定下棋位置方式為,透過 xorshift32()
隨機生成一數字,除 n_moves
並使用函式 fastmod
取餘數,最後取得的數值 i
, available_moves[i]
即為下棋的位置。
因此我們知道函式 fastmod
中的 mod7
為取 7 的餘數, Hacker's Delight 中,函式 rems7
透過 快速計算 具體程式如下:
因為 ,所以我們可以將 n 右移 15 位元並且跟 n 從 lsb 開始計算 15 位元相加,其值取餘數後,與 n 取餘數的值相同。後面的 (r >> 9)
、 (r>> 6)
也是相同的原理。
Hacker's Delight 中提另一種取餘數的方法,利用 的方法取餘數:
透過將 之後再往右移 29 位元,即可在省去高為元的同時,取得我們想要的答案。
而井字遊戲中的函式 mod7
則是結合兩者:
x = (x >> 15) + (x & UINT32_C(0x7FFF));
縮小為數的計算。((x * UINT32_C(0x24924925)) >> 29)
取得餘數。treeint.c
為一個測試程式,分別測試 BST 的插入、搜尋與刪除所花的時間,實作方法為使用結構體 treeint_ops
以函式指標的方式將 treeint_xt.[ch]
所提供的函式製作程 function hook ,並透過 ops
進行函式呼叫。其中,xtree.[ch]
負責處理 xtree 的平衡、新增與刪除,由資料型別 xt_node
作為樹的節點相連接。而 treeint_xt.[ch]
將 xtree.c
中的函式包裝成符合程式所需的資料型別 ,也就是定義了 entry 的資料型別 treeint_st
與比較函式 treeint_xt_cmp
。
treeint_xt.[ch]
提供的 5 個函式,分別透過呼叫 xtree.[ch]
中對應的函式完成對 BST 的操作。而 xtree.[ch]
提供了以下函式:
struct xt_tree *xt_create(cmp_t *cmp, struct xt_node *(*create_node)(void *key), void (*destroy_node)(struct xt_node *n))
:void xt_destroy(struct xt_tree *tree)
:__xt_destroy
使用遞迴得方式移除整棵樹。int xt_insert(struct xt_tree *tree, void *key)
:__xt_find
尋找 key
在 xtree 中插入的位置,並呼叫 __xt_insert
將新節點插入 xtree 中。int xt_remove(struct xt_tree *tree, void *key)
:xt_insert
相似,透過函式 __xt_find
尋找 key
在 xtree 中的位置,呼叫 __xt_remove
將節點從 xtree 中移除。struct xt_node *xt_find(struct xt_tree *tree, void *key)
:__xt_find2
以遞迴的方式尋找 key
在 xtree 中的位置並回傳節點指標。xtree 與 AVL tree 、 Red Black tree 相似,三者皆為自平衡樹, xtree 的平衡機制是利用 hint
來評估是否需要做 rotate, xt_node
的 hint
的計算方式是,其左右節點 hint
最大值 +1 並且只有在被呼叫到 xt_update
時才會被更新。
其中:
xt_update
:xt_balance
判斷左右子節點 hint
的差值是否超過 1 ,如果是則 rotate 進行平衡,接著檢查 hint
在平衡後是否改變,如果是則往上對親節點進行平衡。xt_rotate_right
:xt_rotate_left
:TODO