or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
2022q1 Homework2 (quiz2)
contributed by <
tseng0201
>作業要求
作業進度
測驗一
考慮以下對二個無號整數取平均值的程式碼:
這個直覺的解法會有 overflow 的問題,若我們已知 a, b 數值的大小,可用下方程式避免 overflow:
或是說我們可以看成 \(a/2+b/2\), 但是由於 int 會進行無條件捨去所以我們還要額外處理最後一位的部份, 由真值表可以看出當只有 a 和 b 2進位表示時最後一位都為1時才會發生數值正確的情形在程式中以
a & b & 1
進行處理a + b
溢位接者可以再把加法可以看成 不進位的加法加上其進位
不進位的加法部分為 a ^ b
進位部分為 a & b << 1
再將兩者除以2改成以下等價的實作
組合語言
設定:
GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0"
參數: gcc -Og -s
常使用到的暫存器 :
%rdi : 除存第一個傳入參數
%rsi : 除存第二個傳入參數
%eax : 除存回傳值
版本一
解釋
透過 leal 指令直接將 rdi 與 rsi 相加後存入 eax 等於進行 a + b 後 直接存入esi
對 eax 進行 右移1
回傳
版本二
解釋
1.將a移入%eax
2.計算 a-b 並存入%eax
3.計算(a-b)>>1
4.計算 a + (a-b)>>1
版本三
解釋:
將 a 移入 %eax
右移 %eax 計算 a>>1
將 b 移入 %edx
右移 %edx 計算 b>>1
計算 a>>1 + b>>1 存在 %eax
將 b 移入 %edi
計算 a & b 存在%edi
計算 edi & 1 存在%edi
將 %eax(a>>1 + b>>1) 與 %edi(a & b & 1) 相加後存入 %eax
回傳
版本四
解釋:
將 a 移入 %eax
計算 a + b 並存在 %eax
計算 a ^ b 存在 edi
右移 %edi 得 (a ^ b) >> 1
將 %edi 與 %eax 相加 存入 %eax
回傳
研讀linux average_
常見函式
參考 :
https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
linux/build_bug.h
__builtin_constant_p()
功能 :檢查是否為常數 (constant at compile time), 當該值為常數回傳1,如果不是回傳0
BUILD_BUG_ON()
功能:當條件為真時打斷 complier 並顯示錯誤的條件式
BUILD_BUG_ON_NOT_POWER_OF_2()
功能:檢查該值是否為 2, 的 n 次方 (e.g.,1、2、4…)
WRITE_ONCE
確定寫入時不會被編譯器優化
待完成 :發生場景
探討 linux average.h 其實作
首先查閱 Wikipedia 理解什麼是 Exponentially weighted moving average
可以想成每次都將之前的值以一定比例衰退, 再加上現在的值
其公式為
\[ S_t = \begin{cases} Y_0, & \text{$t$ = 0} \\ \alpha Y_t+(1-\alpha)S_{t-1}, & \text{$t$ > 0} \end{cases} \]
再來觀察 linux/average.h, 依其註解所述該 marco 會建立一個初始的結構與若干個輔助函式 (helper functions), 其參數為 : 名稱、精準度(小數部分的位元長度)及 \(\alpha\) 的倒數, 由於其參數是固定的, 在調用 Macro 時便會固定其值, 所以會看到許多 BUILD_BUG_ON() 用於檢查其參數使否為常數
更新函式為同上述為\(\alpha Y_t+(1-\alpha)S_{t-1}\), 其中\(\alpha = 1 / \text{_weight_rcp}\)
同時為了速度的要求 _weight_rcp 只能為 2 的次方(能夠直接位移)
首先先看看其結構
僅僅只有一個值用於紀錄 EWMA
接下來是初始化的部分,利用 BUILD_BUG_ON 檢查參數是否正確並要求 _precision 必須保留整數的部分, 最後將 EWMA 初始值設為 0
最後是 add 用於計算下一輪的 EWMA 需要傳入 val作為新的值, 使用函式 iloc() 計算 weight_rcp 等同於多少次 向右位移, 並將最終結果寫入 e->internal
READ_ONCE 和 WRITE_ONCE 巨集, 用於避免編譯器優化時忽略或調換指令的順序
註 : 其實現\((1-\alpha)\)方法為 \((s_{t-1}* rcp - s_{t-1}) / rcp\) 避免分數計算及可使用位移與減法實現
應用
測驗二
延伸問題:
請在 Linux 核心原始程式碼中,找出更多類似的實作手法。請善用 git log 檢索。
改寫〈解讀計算機編碼〉一文的「不需要分支的設計」一節提供的程式碼 min,我們得到以下實作 (max):
我們從 a ^ ((EXP4) & -(EXP5)) 開始分析,有兩種情型:
第一種情型
a 是比較大的,我們會希望回傳 a ,此時 ((EXP4) & -(EXP5)) = 0
第二種情型
a 是比較大的,我們會希望回傳 b ,此時我們需要將 a 消去並且產生 b 此時可藉由 a ^ a 會抵消的概念推得((EXP4) & -(EXP5)) = a ^ b, 此時可以推出 EXP4 或 EXP5 其中一個會是 a ^ b
接下來再考慮 & 任何數與0xffffffff做&可以保留原本的數值,反之與0做&只能得到0,故需要一個判斷式決定結果應該是保留 a ^ b 或是得到0, 得 EXP4 或 EXP5 其中一項為 a > b 或是 a < b,再觀察 > 只會產生0或1,0是我們想要的但1不是我們想要的是0xfffffff也就是-1,此時觀察 EXP5 有個負號正是我們所需要的,最後可得下列答案
針對有號整數進行相同 branchless 的實作
在查詢資料的過程中發現有人提到寫成以下方法 並使用gcc -O2 優化時, 會使用 cmovge 指令, 經查詢其為條件移動指令, 不會因為分支預測失敗而不執行, 他是透過 EFLAG 或 RFLAG 的值決定是否進行 move 但是延遲較高, 不一定會比使用分支預測的程式好。
預計實驗 比較上述 branchless 程式與使用 cmove 指令何者較快
測驗三
延伸問題:
考慮以下 64 位元 GCD (greatest common divisor, 最大公因數) 求值函式:
註: GCD 演算法
改寫為以下等價實作:
我們可以從GCD演算法第3點 \(gcd(x,y)=2∗gcd(\dfrac{x}{2},\dfrac{y}{2})\) 以及下列式子得出 shift 作用為記錄執行第3點幾次最後回傳時到時候要乘回去 (RET << shift)
接下來可以觀察到下列式子當 u < v 時,將 v 不斷減去 u 就像是再進行 v % u 而另一部分就像是在做swap
swap後 u = v, v = u - v 我們知道當其中一方為0後 gcd 便可以算完 當 u = v 時才會發生相減為0,而 u - v 由 v 儲存, 至此便可推出 COND 為 v 而 RET 便是 U << shift
在 x86_64 上透過 __builtin_ctz 改寫 GCD,分析對效能的提升(使用linux time 指令測量)
參考來源 : https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html
__builtin_ctz(): 算出從右邊數來的第一個1前有幾個0,當輸入為0為 undefined
由於函式輸入是uint64_t 故使用__builtin_ctzll
效能比較:

使用迴圈進行 gcd64 10000次進行比較
使用 -Og 優化
使用time 進行執行時間測量
原始版

改良版

測驗四
下方程式碼的目地為找出在 bitset 中每個1所在的位置並紀錄在out 並回傳總共有幾個1
首先看向第一個迴圈,便是把 bitset 每次取 64bit 進行處理
再看向第二個迴圈 當 bitset 不為0時繼續尋找該 bitset 中的 1, 首先 t 紀錄目前 bitset 中最右邊的1位置, 透過 bitset & -bitset 取得
原理是負值為相反再加1, bitset相反時0都變成1,所以原本從右邊開始連續的0都會變成連續的1, 再+1後會不斷進位至 bitset 最右邊1的位置此時做&就能剛好保留原本 bitset 中最右邊的1
接下來透過__builtin_ctzll(bitset) 取得最右邊的1是第幾位
透過 out[pos++] = k * 64 + r 紀錄1的位置
最後 再透過 xor 0是保留,1是相反的特性消除最右邊的1,不斷循環直到該 bitset 值為0就取下一組 bitset
應用查詢中
測驗五
測驗六
__ alignof __ 是 GNU extension,以下是其可能的實作方式:
再記憶體中 char c 與 struct _h 可能會以下圖方法排列 (參考 KHLee529 )
透過取得 _h 減去 0 的距離,便可取得 alignment 的大小
測驗七
避免 stream I/O 帶來的影響,可將 printf 更換為 sprintf
研讀 The Fastest FizzBuzz Implementation 及其 Hacker News 討論,提出 throughput (吞吐量) 更高的 Fizzbuzz 實作
過程中,你可能會發現可貢獻到 Linux 核心的空間,請充分討論
考慮貌似簡單卻蘊含實作深度的 FizzBuzz 題目:
從 1 數到 n,如果是 3的倍數,印出 “Fizz”
如果是 5 的倍數,印出 “Buzz”
如果是 15 的倍數,印出 “FizzBuzz”
如果都不是,就印出數字本身
直覺的實作程式碼如下: (naive.c)
以下是利用 bitwise 和上述技巧實作的 FizzBuzz 程式碼: (bitwise.c)
1.透過Faster remainders when the divisor is a constant: beating compilers and libdivide快速計算是否可被 3、5 整除
2.透過計算是否可 整除3 整除5 決定 取得字串 "FizzBuzz%u"的那些部分
我認為兩者比較大的要能落差