# 2021q1 Homework2 (quiz2)
contributed by < `Nahemah1022` >
###### tags: `linux2021`
## 測驗 `1`
### 1. 解釋程式碼,說明其改進空間
```c=
static list_ele_t *get_middle(struct list_head *list)
{
struct list_head *fast = list->next, *slow;
list_for_each (slow, list) {
if (fast == list || fast->next == list)
break;
fast = fast->next->next;
}
return list_entry(slow, list_ele_t, list);
}
```
- `slow` 在 `lost_for_each` loop 中起始位置為 `head`,每次遞增 1
- `fast` 的起始位置為 `head->next`,每次遞增 2,可以保證當
如此一來可以確保當 `slow` 在第 $n$ 個 node 時,`fast` 會在第 $2n$ 個 node,因此當 `fast` 到尾端、或是繞一圈回到 `head` 時 `slow` 會剛好停在第 $ceil(list\_size)$ 個 node,即 linked list 的中間點
---
```c=
static void list_merge(struct list_head *lhs,
struct list_head *rhs,
struct list_head *head)
{
INIT_LIST_HEAD(head);
if (list_empty(lhs)) {
list_splice_tail(lhs, head);
return;
}
if (list_empty(rhs)) {
list_splice_tail(rhs, head);
return;
}
while (!list_empty(lhs) && !list_empty(rhs)) {
char *lv = list_entry(lhs->next, list_ele_t, list)->value;
char *rv = list_entry(rhs->next, list_ele_t, list)->value;
struct list_head *tmp = strcmp(lv, rv) <= 0 ? lhs->next : rhs->next;
list_del(tmp);
list_add_tail(tmp, head);
}
list_splice_tail(list_empty(lhs) ? rhs : lhs, head);
}
```
- 將 `lhs` 與 `rhs` 中的 head node 取出,比較其值大小後將值較小者從原有的 list 中移除,並串接至 `head`
- 當有 `lhs` 或 `rhs` 任一內容為空後,即可將另一方串接至 `head` 後 return
:::danger
開頭的兩個 if 條件式有誤,邏輯上應是用 `list_empty` 判定 `lhs` 或 `rhs` 任一空白,即無須處理空白的 list,直接將另一方串接至 `head` 後結束函式即可,因此程式碼應做以下修改
```diff
if (list_empty(lhs)) {
- list_splice_tail(lhs, head);
+ list_splice_tail(rhs, head);
return;
}
if (list_empty(rhs)) {
- list_splice_tail(rhs, head);
+ list_splice_tail(lhs, head);
return;
}
```
:::
:::warning
**改進空間:**
此處的 `list_del` function 我認為命名得不恰當,delete 應是將其直接從記憶體中釋放的意思,但此處只是要將 node 的前後連接斷開,目的並非刪除,將其改名為 `list_remove` 應會更加符合本意
:::
---
```c=
void list_merge_sort(queue_t *q)
{
if (list_is_singular(&q->list))
return;
queue_t left;
struct list_head sorted;
INIT_LIST_HEAD(&left.list);
list_cut_position(&left.list, &q->list, &get_middle(&q->list)->list
);
list_merge_sort(&left);
list_merge_sort(q);
list_merge(&left.list, &q->list, &sorted);
INIT_LIST_HEAD(&q->list);
list_splice_tail(&sorted, &q->list);
}
```
- 將 queue 透過 `list_cut_position` 從 `get_middle()` function 找到的中點切開,分別將左、右存放置新宣告的 `queue_t left`、以及原來的 `queue_t *q` 之中
- 用 `list_merge_sort` function 將左右的內容個別排序,再用 `list_merge` function 將左右合併
- `q` 的內容重製,並將結果放回 `q` 中即可
:::warning
**改進空間:**
- 在 recursive call 的每一層中真正有被操作到的其實只有 linked list 中的 nodes 本身,卻都需要宣告出一個新的 `queue_t left` 來暫存 merge sort 切割出的左半段,平白消耗 function call stack 中的 memory 空間
- 需要不斷將 queue 的 head 與 tail 重新設置,但每次 merge 又可能會被覆蓋掉,平白增加執行時間
:::
### 2. 將 [lib/list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c) 的實作抽離為 standalone 程式,評測並說明其最佳化手段
---
## 測驗 `2`
### 1. 解釋程式碼運作原理
```c=
/* 找出小於等於 N 且最大的 power-of-2 */
uint16_t func(uint16_t N) {
/* change all right side bits to 1 */
N |= N >> 1;
N |= N >> 2;
N |= N >> 4;
N |= N >> 8;
return (N + 1) >> 1;
}
```
在 binary 的數值系統,一個數是 power-of-2 代表其所有 bits 中只有唯一一個 bit 是 `1`,其餘皆為 `0`,因此可以將題意轉為找出一個小於 N 、且符合上述條件的最大數,也就是要算出將位於最左邊的 `1` 保留,其餘 bits 都歸 `0` 的數
但本題的實做方式為將所有位於最左邊的 `1` (以下簡稱為 MS1) 右方的 bits 都轉成 `1`,再將這個結果 + 1 後往右 shift 一位,也可以得到相同的結果
本題將所有位於 MS1 右方的 bits 都轉成 `1` 的方式為先右移一位後與原數做 `|=`,可以確保在 MS1 正右方的 bit 將被轉變為 `1`,再右移兩位與原數做 `|=` 可以確保 MS1 右方的 3 個 bits 都會被轉變為 `1`,重複上述算法至右移 8 位即可得出答案
---
## 測驗 `3`
### 1. 解釋程式碼運作原理
```c=
#include <stdint.h>
void bitcpy(void *_dest, /* Address of the buffer to write to */
size_t _write, /* Bit offset to start writing to */
const void *_src, /* Address of the buffer to read from */
size_t _read, /* Bit offset to start reading from */
size_t count)
{
size_t read_lhs = _read & 7;
size_t read_rhs = 8 - read_lhs;
const uint8_t *source = (const uint8_t *) _src + (_read / 8);
size_t write_lhs = _write & 7;
size_t write_rhs = 8 - write_lhs;
uint8_t *dest = (uint8_t *) _dest + (_write / 8);
static const uint8_t read_mask[] = {
0x00, /* == 0 00000000b */
0x80, /* == 1 10000000b */
0xC0, /* == 2 11000000b */
0xE0, /* == 3 11100000b */
0xF0, /* == 4 11110000b */
0xF8, /* == 5 11111000b */
0xFC, /* == 6 11111100b */
0xFE, /* == 7 11111110b */
0xFF /* == 8 11111111b */
};
static const uint8_t write_mask[] = {
0xFF, /* == 0 11111111b */
0x7F, /* == 1 01111111b */
0x3F, /* == 2 00111111b */
0x1F, /* == 3 00011111b */
0x0F, /* == 4 00001111b */
0x07, /* == 5 00000111b */
0x03, /* == 6 00000011b */
0x01, /* == 7 00000001b */
0x00 /* == 8 00000000b */
};
while (count > 0) {
uint8_t data = *source++;
size_t bitsize = (count > 8) ? 8 : count;
if (read_lhs > 0) {
data <<= read_lhs;
if (bitsize > read_rhs)
data |= (*source >> read_rhs);
}
if (bitsize < 8)
data &= read_mask[bitsize];
uint8_t original = *dest;
uint8_t mask = read_mask[write_lhs];
if (bitsize > write_rhs) {
/* Cross multiple bytes */
*dest++ = (original & mask) | (data >> write_lhs);
original = *dest & write_mask[bitsize - write_rhs];
*dest = original | (data << write_rhs);
} else {
// Since write_lhs + bitsize is never >= 8, no out-of-bound access.
mask |= write_mask[bitsize - write_lhs];
*dest++ = (original & mask) | (data >> write_lhs);
}
count -= bitsize;
}
}
```
:::info
**程式中變數 read/write、lhs/rhs 代表的意義:**
程式的實作策略為將 `_src` 中的 bits 一次一個 byte 搬移至 `_dest` 中,由於在 C 語言中存取一個變數的最小單位為 byte,因此若 bit offset 沒有剛好是 8 的倍數,則在開頭要存取的 byte 中會有部分的 bits 並不需要被讀取/寫入,非開頭存取的 byte 中也會有部份的 bits 在上一步中已經被讀取/寫入過了,應要跳過
因此,在讀取中的每個 byte 內,程式以 `read_lhs` 代表位於左半部不需要被讀取(要跳過)的 bits 數量, `read_rhs` 代表位於右半部真正需要讀進來的 bits 數量,寫出時的 `write_lhs` 、`write_rhs` 亦同理
:::
- 讀取出至多一個 byte,暫存至 `data` 中
- 用 `_src + (_read / 8)` 找到第一個有位元需要被讀取的 byte 位置,把值取出至 `data` 中
- 用 `data <<= read_lhs` 將左側不需要被讀取的位元跳過,留下需要的部份(共 `read_rhs` 個位元)
- 若本次要讀取的位元數量超過 `read_rhs` 個位元(前兩步已經處理好了的部份),則須跨越到下一個 byte 讀取:
- 讀出 `read_lhs` 個位元放至 `data` 的右側,也就是 `data |= (*source >> read_rhs)` ,即可填滿這次要讀取的一整個 byte
- 若本次要讀取的位元不滿一整個 byte,則透過 `read_mask` 將尾端多讀進 `data` 的部份去除
- 將 `data` 中暫存的資料寫出
- 用 `_dest + (_write / 8)` 找到第一個需要被寫入位元的 byte 位置
- 若本次處理的位元數量比當前的 byte 放得下的位元數量還多(即 `bitsize > write_rhs`)時,則需要跨越到下一個 byte 去做寫入:
- 用 `data >> write_lhs` 讓 `data` 中只剩下當前的 byte 放得下的位元數量(即 `write_rhs` 個位元)並且靠右
- 再用 `original & mask` 確保左側值不會被改到後寫入 `dest`
- 跨越到下一個 byte,將 `original` 更新為下一個 byte 還需要寫入的位元數量(`bitsize - write_lhs` 個位元)後再次寫入
- 若本次處理的的位元數量不足填滿第一個 byte( `bitsize < write_rhs` ),則須注意不可讓超出 `bitsize` 個數的位元被修改到
- 將 `mask` 的值後段也設為 `1` 即可避免
- 即 `mask |= write_mask[bitsize - write_lhs]`
### 2. 說明其改進空間
:::info
**branch miss 所造成的效能浪費:**
程式中的 if 條件式在被編譯為執行檔時,會被轉換成組合語言中 branch 相關的 CPU instruction,若 CPU 處理 instruction 時有用到 pipeline 的架構,則會需要做 branch prediction 來預測之後 instruction 的執行順序,但若預測失敗(即所謂的 branch miss)則會造成往後所有處理到一半的 instruction 需要 revert 到 predict 前的狀態,平白浪費 CPU cycle,因此若可以減少程式中不必要的 if 條件式,可以有效提升程式的效能
:::
這段 `bitcpy` 程式中我發現在讀取的過程中,因為最後都能夠用 `read_mask` 將 `data` 中不需要的部分剔除,因此在這之前其實不需要刻意去調整 data 要讀取的 range 有多大、以及有沒有跨越到下一個 byte,直接將完整一個 byte 的大小讀取出來,最後再用 `read_mask` 留下需要的部分即可,因此程式可以改成如下,將所有 if 條件式移除:
```diff=
size_t bitsize = (count > 8) ? 8 : count;
- if (read_lhs > 0) {
data <<= read_lhs;
- if (bitsize > read_rhs)
data |= (*source >> read_rhs);
- }
- if (bitsize < 8)
data &= read_mask[bitsize];
...
```
此外,程式中另外宣告的兩個 `mask`,在使用時需要來回查看其內容,使用上有些許不便,額外宣告這兩個陣列也會增加記憶體 stack 空間的使用,應該有更好的實作方式,我觀察後發現兩陣列的內容有規律性,其實透過 bitwise 的操作就可以直接完成了,取代方式如下:
```c
read_mask[i] == 0xFF << (8 - i)
write_mask[i] == 0xFF >> i
```
因此最終完成的程式如下:
> [GitHub](https://github.com/Nahemah1022/Linux2021-quizzes/blob/main/quiz2/bitcpy/bitcpy.c)
```c=
void bitcpy(void *_dest, /* Address of the buffer to write to */
size_t _write, /* Bit offset to start writing to */
const void *_src, /* Address of the buffer to read from */
size_t _read, /* Bit offset to start reading from */
size_t count)
{
size_t read_lhs = _read & 7;
size_t read_rhs = 8 - read_lhs;
const uint8_t *source = (const uint8_t *) _src + (_read / 8);
size_t write_lhs = _write & 7;
size_t write_rhs = 8 - write_lhs;
uint8_t *dest = (uint8_t *) _dest + (_write / 8);
while (count > 0) {
uint8_t data = *source++;
size_t bitsize = (count > 8) ? 8 : count;
data <<= read_lhs;
data |= (*source >> read_rhs);
data &= 0xFF << (8 - bitsize);
uint8_t original = *dest;
uint8_t mask = 0xFF << (8 - write_lhs);
if (bitsize > write_rhs) {
/* Cross multiple bytes */
*dest++ = (original & mask) | (data >> write_lhs);
original = *dest & (0xFF >> (bitsize - write_rhs));
*dest = original | (data << write_rhs);
} else {
// Since write_lhs + bitsize is never >= 8, no out-of-bound access.
mask |= (0xFF >> (bitsize - write_lhs));
*dest++ = (original & mask) | (data >> write_lhs);
}
count -= bitsize;
}
}
```