contributed by < JimmyLiu0530
>
進階電腦系統理論與實作
兩個整數間的 Hamming distance 為其二進位的每個位元的差。請計算輸入參數兩整數 x 與 y 的 Hamming distance,例如整數 1 的二進位為 0 0 0 1,而整數 4 的二進位為 0 1 0 0,則 1 與 4 的 Hamming distance 為 2。
唯有兩數的某一位元相異,他們的 hamming distance 才會加1,因此先對兩數 x, y 作 ^
,再利用 __builtin_popcount
計算有幾個位元為1,即為 hamming distance,所以OP = ^
。
仿照延伸問題2的做法,詳細的原理見下方。
假設在某一位元共有 oneCount
個數字為1,那麼就有 (n - oneCount)
個數字為0,因為陣列中的每個數字皆會與其他數字計算過一次 hamming distance,所以就會使 hamming distance 總和增加 oneCount * (n - oneCount)
。如此一來,遍歷所有位元的總和即為最後的答案。
- 搭配閱讀 Hamming codes, Part I 及 Hamming codes, Part II,你可適度摘錄影片中的素材,但應當清楚標註來源
Hamming weight, 記作 , 表示 x 向量有幾個位元為1; 而 Hamming distance, 記作 , 表示兩個向量有幾個不同的位元,又可透過相加兩向量再取其 hamming weight 得到,因此可以寫成 (即 = )。
假設模擬從 sender 取得的16個位元,其中11個 data bits,4個 parity bits:
接著針對所有位元為1的作 XOR,會得到:
為了讓上述的 XOR 結果為0,9的二進位為1001
,因此對第 1 及第 8 個進行位元反轉:
接下來我們就可以模擬資料傳送過程中受干擾的情況,例如假設第 10 個位元在傳送中受到干擾,XOR 結果就會得到 10,也就是錯誤的位元,因此可以馬上找到錯誤的位元加以修正:
給定一棵樹,樹上有 n 個節點,編號自 0 到 n−1。樹以父節點陣列的形式提供,其中 parent[i] 是節點 i 的父節點。樹的根節點是編號為 0 的節點。請設計 treeAncestorGetKthAncestor(TreeAncestor *obj, int node, int k) 函式,回傳節點 node 的第 k 個祖先節點。若不存在這樣的祖先節點,回傳 -1。樹節點的第 k 個祖先節點是從該節點到根節點路徑上的第 k 個節點
首先,根據 line 10-11及15得知 obj->parent
是一個指標的指標,因此 AAA = int **parent
。
此題的原理: 假設要找某節點的第 k 個祖先,k = 3 (= ) 為例,那麼意同於找此節點的第 個祖先的第 個祖先。
為甚麼要這樣做?
因為 n 個點的樹在最差的情況下有 n-1 個祖先,若將每個節點從第 0 到第 n-1 個祖先記錄下來,則需要 O() 的空間,但取得第 k 個祖先只需要 O(1) 的時間。相反的,若不額外記錄各節點的祖先,時間複雜度就會提高。因此本方法能在時間與空間取一個平衡 - 僅記錄第 、、…、 個祖先。
obj->parent[i][j] 的意義就是第 i 個節點的第 個祖先是誰,所以 line 14-20 就在對此二維陣列初始化及設定每個節點的第一個祖先。
接著,line 22-31 建立每個節點 i 的第 個祖先,會先看此節點是否存在第 個祖先,
BBB = -1
。最後呼叫 treeAncestorGetKthAncestor
時,根據上述第二段的原理去拆解 k ,因此 CCC = 1 << i
。
TreeAncestor
struct 中,新增 int n;
並在 treeAncestorCreate
中,新增 obj->n = n;
減少使用的記憶體
在原本的程式碼中,之所以 max_level = 32 - __builtin_clz(n) + 1;
需要 +1
,為的就是多額外配置空間給 obj->parent[i] ,好讓底下 line 22 的 for
不會越界存取。(因為這裡 for
是靠 quit
來離開迴圈的)
也就是說若新增 for 的中止條件,就不用擔心越界,也不需要配置額外空間,來達到節省記憶體的目的。
因此,修改後的部分程式碼如下:
修改前的程式碼需要 70.3 MB 的記憶體空間,而修改過後只需 68.5 MB,減少了 1.8 MB。
考慮貌似簡單卻蘊含實作深度的 FizzBuzz 題目:
直覺的實作程式碼如下: (naive.c
)
觀察 printf
的(格式)字串,可分類為以下三種:
"%d"
: 長度為 2 B考慮下方程式碼:
我們若能精準從給定輸入 i
的規律去控制 start
及 length
,即可符合 FizzBuzz 題意:
以下是利用 bitwise 和上述技巧實作的 FizzBuzz 程式碼: (bitwise.c
)
gcc-9 還內建了 FizzBuzz optimization (Bug 82853 - Optimize x % 3 == 0 without modulo)。
首先,回顧一下 quiz 2 所學的快速 mod 運算即可了解 line 3-4, 7-8 及 12-13 的原理,在此不贅述。
根據題目給的提示可以整理成下列四種可能:
被n整除 | start | length |
---|---|---|
n = 3 | 0 | 4 |
n = 5 | 4 | 4 |
n = 15 | 0 | 8 |
X | 8 | 2 |
(X:不被3或5整除) |
再回頭看程式碼,在 line 12-13,div3
及 div5
若為 1,代表 i
分別被 3 跟 5 整除,否則為0,於是根據上面表格可以推算出 length = (2 << div3) << div5
。
處理完 length
接著換 start
。在 line 17,此處(MSG_LEN >> KK1) >> (KK2 << KK3)
即為 start
,一樣根據上表,即可推得 KK1 = div5
, KK2 = div3
, KK3 = 2
。
naive.c
和 bitwise.c
效能落差
- 避免 stream I/O 帶來的影響,可將
printf
更換為sprintf
bitwise.c
程式碼,試圖運用更少的指令來實作出 branchless;考慮到整數 PAGE_QUEUES
可能有以下數值:
給定 size_t offset
,試著改善原本運算:
由於 PAGE_QUEUES
的數值分佈已知,在整數除法時,可思考加速運算的機制。需要注意,某些硬體平台缺乏整數除法指令 (如 Arm Cortex-A8),即使 Intel 公司出品的 Core 處裡器 Sandy Bridge 微架構中,針對 64 位元整數的除法運算,會佔用 40 到 103 個處理器週期,運算成本仍屬沉重。
來源: Agner Fog’s instruction tables,第 180 頁
於是我們可預先進行計算,從而避免整數除法指令的使用: (假設在 x86_64 硬體執行 GNU/Linux 並透過 gcc/clang 編譯)
其中 CASES 可能形如:
這樣的組合,請用最少的 case-break 敘述做出同等size_t blockidx = offset / PAGE_QUEUES;
效果的程式碼。
此方法原理就是先對被除數以及除數同時右移 ffs32(divident)
位元,剩下的運算再交給 case 處理。
divident(dec) | divident(binary) | divident >> ffs(divident) |
---|---|---|
1* | 00010…0 | 0001=1 |
2* | 00100…0 | 0001=1 |
3* | 00110…0 | 0011=3 |
4* | 01000…0 | 0001=1 |
5* | 01010…0 | 0101=5 |
6* | 01100…0 | 0011=3 |
7* | 01110…0 | 0111=7 |
8* | 10000…0 | 0001=1 |
根據上面張表可得知 CASES 至少需要包含 1, 3, 5, 7 這四個數字,但又因為任何數除以 1 還是原數,所以只需 3, 5, 7 三數即可。
__builtin_ffs
,大幅降低記憶體開銷程式說明:
CheckQueen
檢查是否 Queen 可以放在此位置
NQueens(row+1)
)PushAnswer
將結果存入 res
***solveNQueens(int n, int *returnSize, int **returnColumnSizes)
*returnSize
: 記錄 res
的 2D array 數量(即 ways[n]
)**returnColumnSizes
: 記錄每個 2D array 的行數(即 n
)QueenPos
: 記錄各列 Queen 所在的行數(即 QueenPos[1]=3 代表 Queen在第一列第三行)***res
: 記錄每個答案的棋盤擺放方式NQueens(int row, int n)
checkQueen(int row, int col)
row
以上的所有格子看是否已存在 Queen 使得
col
同一行 或(row, col)
的右上或左上(row, col)
可以放 Queen,反之,則不行放StoreAnswer(int n)
QueenPos
將一種擺放方式存到 res
中
'Q'
,否則為 '.'