sysprog
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit
      • Invitee
      • No invitee
    • Publish Note

      Publish Note

      Everyone on the web can find and read all notes of this public team.
      Once published, notes can be searched and viewed by anyone online.
      See published notes
      Please check the box to agree to the Community Guidelines.
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Help
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit
Invitee
No invitee
Publish Note

Publish Note

Everyone on the web can find and read all notes of this public team.
Once published, notes can be searched and viewed by anyone online.
See published notes
Please check the box to agree to the Community Guidelines.
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
3
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
--- tags: linux2022 --- # 第 1,2 週課堂問答簡記 ## `Uduru0522` Linux 核心的 hash table 實作中,用以處理 hash 數值碰撞的 `hlist_node`: ```cpp struct hlist_node { struct hlist_node *next, **pprev; }; ``` **其中 `pprev` 為何要宣告為==指標的指標==?** 探討典型 doubly-linked list delete 會產生的問題。 考慮我們使用典型的 doubly-linked list: ```c struct dll_node { struct dll_node *next, *prev; } ``` > `dll` 是 doubly-linked list 的簡稱 於是資料結構會展現如下圖: ```graphviz digraph G { rankdir = LR; splines = false; node[shape = "record"] list_head[label = "<m>list_head | <n>first"] node_1[label = "<m>dll_node_1 | {<p>prev | <n>next}", group = list]; node_2[label = "<m>dll_node_2 | {<p>prev | <n>next}", group = list]; node_3[label = "<m>dll_node_3 | {<p>prev | <n>next}", group = list]; NULL_1, NULL_2[shape = plaintext, label = "NULL", group = list] {rank = "min" list_head NULL_1} list_head -> node_1:m; node_1:p -> NULL_1 node_1:n -> node_2:m; node_2:p -> node_1:m; node_2:n -> node_3:m; node_3:p -> node_2:m; node_3:n -> NULL_2; } ``` 注意: + 除了末端,第一個節點的 `prev` 指標內容也是 `NULL`。 + `prev` 及 `next` 皆指向相鄰的**節點本身**。 嘗試撰寫 `delete_node():` ```c void dll_delete_node(struct list_head *l, struct dll_node *n) { struct dll_node *prev = n->prev, *next = n->next; if (next) next->prev = prev; // Delete and update where list_head points to, // which requires the list_head to be passed in. if (!prev) { l->first = next; } else { prev->next = next; } } ``` 可發現當我們要移除第一個節點時,必須做出額外的操作來更新 `list_head` 指向的節點,於是除了移除的目標之外,還必須傳入 `list_head`。 當我們用指標的指標改寫上述程式碼,將原本的 `struct dll_node *prev` 變更為 `struct dll_node **pprev`: ```c struct hlist_node { struct hlist_node *next, **pprev; } ``` 則會形成以下的結構: ```graphviz digraph G { rankdir = LR; splines = false; node[shape = "record"] list_head[label = "<m>list_head | <n>first"] node_1[label = "<m>hlist_node_1 | {<p>pprev | <n>next}", group = list]; node_2[label = "<m>hlist_node_2 | {<p>pprev | <n>next}", group = list]; node_3[label = "<m>hlist_node_3 | {<p>pprev | <n>next}", group = list]; NULL[shape = plaintext, label = "NULL", group = list] {rank = "min" list_head} list_head -> node_1 -> node_2 -> node_3[ weight = 10, style = invis ] list_head:n -> node_1:m; node_1:p -> list_head:n; node_1:n -> node_2:m; node_2:p -> node_1:n; node_2:n -> node_3:m; node_3:p -> node_2:n; node_3:n -> NULL; } ``` 注意: + 僅有末端指標內容是 `NULL`。 + `next` 指向相鄰的**節點本身**,而 `pprev` 指向**指著自身節點的指標**。 則我們便可藉由下方程式碼來撰寫 `delete_node()`: ```c void hlist_delete_node(struct hlist_node *n) { struct hlist_node *next = n->next; **pprev = n->pprev; // Since there is always a pointer which points to node n, // no special case is needed to deal with. *pprev = next; if (next) next->pprev = pprev; } ``` 跟典型 doubly-linked list 的實作做比較,由於該實作的 `prev` 指標必須指向 `struct dll_node` 型別,導致第一個節點會出現指向 `NULL` 的狀況;而將其替換成指標的指標後,便可順利消除這個特例。 至此,我們理解到,hash list 是為雜湊表特製的鏈結串列,儘管雙向鏈結串列亦可實作雜湊表,但由於後者開頭和結尾需要各用 2 個指標,當 bucket 很大時,會增加記憶體開銷。 相較於 `list_head` 的兩個指標,`hlist_head` 僅需要保存一個指標,`hlist_node` 的 `next` 指向下個節點,但 `pprev`(指標的指標) 並不指向上個節點,而是指向上個節點的 `next` 指標,這樣設計的好處是,當要刪除的節點是首個節點,即可藉由 `*pprev = next` 直接修改指標的指向。 延伸閱讀: * [核心基礎設施: `hlist_head`/`hlist_node` 結構解析](http://linux.laoqinren.net/kernel/hlist/) * [hlist 資料結構圖示說明](https://zhuanlan.zhihu.com/p/82375193) --- ## `Eddielin0926` ### quiz2-4 先觀察原本函式的功能 - `bitmap` 是要處理的資料,以 64 bits 為區間 - `bitmapsize` 是總共有要處的資料大小 - `out` 是結果存放的位置 在第一層迴圈中 `bitset` 會儲存目前處理資料,第二層的迴圈目的是要找到資料的位元是 1 的位置並且去除掉 1 後,將位置記錄在 `out` 中。 ```c #include <stddef.h> size_t naive(uint64_t *bitmap, size_t bitmapsize, uint32_t *out) { size_t pos = 0; for (size_t k = 0; k < bitmapsize; ++k) { uint64_t bitset = bitmap[k]; size_t p = k * 64; for (int i = 0; i < 64; i++) { if ((bitset >> i) & 0x1) out[pos++] = p + i; } } return pos; } ``` 嘗試一個簡單的[例子](https://onlinegdb.com/qxgIaXhvp) ``` bitmap[0] = 0x11 bitmapsize = 1 ``` 我們可以得到 `bitmap` 中 1 的數量以及 1 的位置 ``` pos = 2 out = {0, 4} ``` 接下來透過 [`__builtin_ctzll`](https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html) 將函式改寫 `__builtin_ctzll` 用來找到由低位往高位的第一個 1 中間會有幾個 0,因此可以用來取代重複判斷的 `if ((bitset >> i) & 0x1)` ,就能減少分支的次數,在硬體有支援的情況下可以加速運算,再來看 `while` 的條件是 `bitset != 0`,加上 `__builtin_ctzll` 的特性,因此我們可以知道,當我們找到 `bitset` 的 1 時,要將 1 去處除掉,才有辦法利用 `__builtin_ctzll` 計算 1 的位置。 ```c #include <stddef.h> size_t improved(uint64_t *bitmap, size_t bitmapsize, uint32_t *out) { size_t pos = 0; uint64_t bitset; for (size_t k = 0; k < bitmapsize; ++k) { bitset = bitmap[k]; while (bitset != 0) { uint64_t t = bitset & -bitset; int r = __builtin_ctzll(bitset); out[pos++] = k * 64 + r; bitset ^= t; } } return pos; } ``` 當我們在思考如何去除掉某個位元的 1 時,第一個想法可能會是與一個在對應位置的 0 做 bitwise AND ```c bitset &= ~(1 << r) ``` <!-- ``` bitset = ((bitset >> (r + 1)) << r) ``` --> 但在這邊的做法利用了**二補數**來去除最低位元的 1。 ```c bitset ^= bitset & -bitset ``` 在我們剛剛的計算中,我們目標都是 `bitset` 中最低位的 1,例如 XXX**1**00。 當我們對 `bitset` 做二補數計算時,最低位的 1 左邊的位元會是原本的補數,右邊則會與原本一樣。 $$ 0110100_2 \xrightarrow{取二補數} 1001100_2 \\ 0110100_2 \land 1001100_2 = 0000100_2 \\ 0110100_2 \oplus 0000100_2 = 0110000_2 $$ 與原本的 `bitset` 做 XOR 就能將最右邊的 1 變成 0 了。 ### quiz2-1 對二個無號整數取平均值 ```c #include <stdint.h> uint32_t average(uint32_t a, uint32_t b) { return (a >> 1) + (b >> 1) + (a & b & 1); } ``` `a >> 1` 和 `b >> 1` 代表將 a 和 b 個別除以 2,`a & b & 1` 用來檢查兩個數是不是都是奇數。 另一個對二個無號整數取平均值的實作。 ```c uint32_t average(uint32_t a, uint32_t b) { return (a & b) + ((a ^ b) >> 1); } ``` 我認為可以想像成全加器除二。 ![](https://hackmd.io/_uploads/B1un5A2ec.png) 原本是 `(a + b) >> 1`,將 carry 提出來後就變成 `(a & b) + ((a ^ b) >> 1)`。 --- ## `william40614`, `millaker` 先分析原程式,參考〈[解讀計算機編碼](https://hackmd.io/@sysprog/binary-representation)〉一文的「不需要分支的設計」一節提供的程式碼 `min`,改寫為以下程式碼: (其中 `EXP4` 和 `EXP5` 尚未實作) ```c #include <stdint.h> uint32_t max(uint32_t a, uint32_t b) { return a ^ ((EXP4) & -(EXP5)); } ``` 運用 [The XOR trick](https://florian.github.io/xor-trick/) 提及的規律: * `a ^ a = 0` * `a ^ 0 = a` * `a ^ ANY ^ ANY = a` 關於 `EXP5`,只要 `a < b` 成立,也就是 `1`, 加上負號後得到 `-1` (`0xffffffff`),原本的表示式成為: * `-1 & (a ^ b) = (a ^ b)` * `a ^ a ^ b = 0 ^ b = b` 可推得 max 是 b 。相反地,當 `a > b` 成立,後面一整項為 `0`,回傳值就會是 `a`。 --- ## `Tcc0403` 第 2 周測驗 3 ### 分析程式碼 ```c #include <stdint.h> uint64_t gcd64(uint64_t u, uint64_t v) { if (!u || !v) return u | v; int shift; for (shift = 0; !((u | v) & 1); shift++) { u /= 2, v /= 2; } while (!(u & 1)) u /= 2; do { while (!(v & 1)) v /= 2; if (u < v) { v -= u; } else { uint64_t t = u - v; u = v; v = t; } } while (v); return u<<shift; } ``` GCD 演算法 1. If both x and y are 0, gcd is zero $gcd(0, 0) = 0$. 2. $gcd(x, 0) = x$ and $gcd(0, y) = y$ because everything divides 0. 3. If x and y are both even, $gcd(x, y)=2 \times gcd(\dfrac{x}{2},\dfrac{y}{2})$ because $2$ is a common divisor. Mulitplication with $2$ can be done with betwise shift operator. 4. If x is even and y is odd, $gcd(x, y) = gcd(\dfrac{x}{2}, y)$. 5. On similar lines, if x is odd and y is even, then $gcd(x, y) = gcd(x, \dfrac{y}{2})$. It is because $2$ is not a common divisor. 以下分段說明: ```c if (!u || !v) return u | v; ``` $\to$ 對應至 GCD 演算法第 2 點 * $gcd(x,0)=x$ and $gcd(0, y)=y$ because everything divides 0. 若 u 和 v 中其中一數為 0 ,則直接回傳另一數 > `x | 0` 會得到 x ```c int shift; for (shift = 0; !((u | v) & 1); shift++) { u /= 2, v /= 2; } ``` $\to$ 對應至 GCD 演算法第 3 點 * If x and y are both even, $gcd(x, y) = 2 \times gcd(\dfrac{x}{2},\dfrac{y}{2})$ because $2$ is a common divisor. Mulitplication with $2$ can be done with betwise shift operator. 先取出`u` 和 `v` 之間屬於 $2^n$ 的公因數,`shift` 即為最大的 $n$ ```c while (!(u & 1)) u /= 2; ``` $\to$ 對應至 GCD 演算法第4 點 * If x is even and y is odd, $gcd(x, y) = gcd(\dfrac{x}{2}, y)$. 當 `u` 為偶數時,可以除以 $2$ ```c do { while (!(v & 1)) v /= 2; if (u < v) { v -= u; } else { uint64_t t = u - v; u = v; v = t; } } while (v); ``` $\to$ 其中 `while(!(v &1)) v /= 2;` 對應至 GCD 演算法第 5 點 * On similar lines, if x is odd and y is even, then $gcd(x, y) = gcd(x, \dfrac{y}{2})$. It is because $2$ is not a common divisor. 可以透過迴圈不變性 (loop invariant) 來幫助解讀程式 * `u` 為被除數 * `v` 為除數 當除數 `v`為 0 時結束迴圈,被除數 `u` 為進入迴圈前 `u` 和 `v` 的最大公因數 :::warning 事實上 `u` 在迴圈前不一定是被除數,因為先前透過 GCD 演算法第 4 點去除不必要的計算 ::: ```c return u << shift; ``` 最後回傳最大公因數時,乘上原先取出屬於 $2^n$ 的公因數,可以透過左移達成 ### 透過 `__builtin_ctz` 改寫程式 已知除法運算會耗費較多時間,我們可以利用 `__builtin_ctz` (編譯器會產生對應到現代微處理器的專門指令) 搭配上述針對二進位特性調整的演算法,減少除法的使用 改寫之前先透過 perf 分析程式,將一百萬組由 `rand` 函式生成的亂數傳入 `gcd64` 函式執行 ```shell $ gcc -g3 -O0 gcd.c -o gcd $ perf record ./gcd $ perf annotate ``` ![](https://hackmd.io/_uploads/BksxZ3fl9.png) 由上圖可以發現,花費 CPU 週期數最多的段落為迴圈當中的 `v /= 2` 運算,佔據約 28% 以下為改寫過後的程式 ```c uint64_t gcd64_ctz(uint64_t u, uint64_t v) { if (!u || !v) return u | v; int shift = __builtin_ctzll(u | v); u = u >> __builtin_ctzll(u); do { v = v >> __builtin_ctzll(v); if (u < v) { v -= u; } else { uint64_t t = u - v; u = v; v = t; } } while (v); return u << shift; } ``` 透過 perf 分析前後差異 ```shell $ gcc -g -O0 gcdcmp.c -o gcdcmp $ perf record ./gcdcmp $ perf report ``` ![](https://hackmd.io/_uploads/Sk_53nGxc.png) 將同樣一百萬筆由 `rand` 函式所產生的亂數傳入 `gcd64` 和 `gcd64_ctz` 函式比較,發現改寫過後的版本花費時間幾乎是原來的一半 ### 改進空間 透過 perf 分析改寫程式 ```shell $ gcc -g -O0 gcd_ctz.c -o gcd_ctz $ perf record ./gcd_ctz $ perf annotate ``` ![](https://hackmd.io/_uploads/r1YR03zxq.png) 佔據花費時間最多的為分支指令 (如 [jne](https://www.aldeid.com/wiki/X86-assembly/Instructions/jnz)),其次是迴圈內部的減法運算 --- ## `kdnvt` 為什麼有 `list_for_each_safe` 還需要 `list_for_each`? `list_for_each` 是 `list.h` 中定義的巨集,用來走訪 linked list 中的各個節點。 以下是實作方式: ``` #define list_for_each(pos, head) \ for (pos = (head)->next; !list_is_head(pos, (head)); pos = pos->next) ``` 程式的邏輯十分單純,判斷 `pos` 是不是等於 `head` 來當作結束條件,並在每次迴圈結尾讓 `pos = pos->next` 來走訪。但如果在走訪的過程中,改變了 `pos->next` ,就可能造成不可預期的行為。因此有了 `list_for_each_safe` 。 `list_for_each_safe` 會用到的地方,主要在走訪的過程中,會更改到當前節點(如將當前節點移出)的時候。 想要知道它是如何在更改節點的時候,仍然可以正確的走訪各個節點,就是直接看這個巨集的實作細節: ``` #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; \ !list_is_head(pos, (head)); \ pos = n, n = pos->next) ``` 可以看到除了當前的節點 `pos` ,它還用了另一個指標 `n` 去指向下一個節點。當目前的節點執行完了,當會在迴圈的結尾讓 `pos = n` ,再讓 `n` 指向 `pos->next` 。如此一來,就算 `pos` 在走訪的過程被移出,也有 `n` 這個指標去暫存下一個節點的位址,所以可以安全的走訪所有節點。 若沒有要改變節點的連接方式,如 `q_size` 中走訪各節點被計算總數,則使用 `list_for_each` 就好。如果在這種情況下還使用 `list_for_each_safe` ,則要額外準備一個指標去暫存下一個節點,然而這個指標在走訪個過程中其實是不需要的。 ## `mickey30606` 如何將以下程式碼,修改成取得最接近且小於等於 2 的冪的值。 ```clike uint64_t next_pow2(uint64_t x) { x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 1; x |= x >> 8; x |= x >> 16; x |= x >> 32; return x+1; } ``` - 方法一:我們只需要將目前最高位的 1 後面位元全部都變成 0 就可以得到我們想要的結果,而原本程式就已經將 `x` 後面位元全部都變成 1 ,所以我們只需要將 `x` 往右推移 1 並與自己進行 XOR 即可得到答案。 ```clike uint64_t next_pow2(uint64_t x) { x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; x |= x >> 32; return x ^ (x >> 1); } ``` - 方法二:使用 `__builtin_clzl` 進行改寫,取得最高位的 1 前面有幾個 bit 之後,用 1 進行推移就可以得到答案。 ```clike uint64_t next_pow2(uint64_t x) { return 1 << (63 - __builtin_clzl(x)); } ```

Import from clipboard

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template is not available.
Upgrade
All
  • All
  • Team
No template found.

Create custom template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

How to use Slide mode

API Docs

Edit in VSCode

Install browser extension

Get in Touch

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Upgrade to Prime Plan

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

No updates to save
Compare with
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

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.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Upgrade

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Upgrade

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully