Appmedia
    • Create new note
    • Create a note from template
      • 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
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • 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
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
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
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    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
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # 2024q1 Homework6 (integration) contributed by < [Appmedia06](https://github.com/Appmedia06) > ## 開發環境 ``` $ gcc --version gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Vendor ID: GenuineIntel Model name: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz CPU family: 6 Model: 140 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 Stepping: 1 CPU max MHz: 4200.0000 CPU min MHz: 400.0000 BogoMIPS: 4838.40 Virtualization features: Virtualization: VT-x Caches (sum of all): L1d: 192 KiB (4 instances) L1i: 128 KiB (4 instances) L2: 5 MiB (4 instances) L3: 8 MiB (1 instance) NUMA: NUMA node(s): 1 NUMA node0 CPU(s): 0-7 ``` ## 自我檢查清單 - [x] 研讀前述 ==Linux 效能分析== 描述,在自己的實體電腦運作 GNU/Linux,做好必要的設定和準備工作 $\to$ 從中也該理解為何不希望在虛擬機器中進行實驗; - [x] 閱讀〈[Linux 核心模組運作原理](https://hackmd.io/@sysprog/linux-kernel-module)〉並對照 Linux 核心原始程式碼 (v6.1+),解釋 `insmod` 後,Linux 核心模組的符號 (symbol) 如何被 Linux 核心找到 (使用 List API)、`MODULE_LICENSE` 巨集指定的授權條款又對核心有什麼影響 (GPL 與否對於可用的符號列表有關),以及藉由 [strace](https://man7.org/linux/man-pages/man1/strace.1.html) 追蹤 Linux 核心的掛載,涉及哪些系統呼叫和子系統? - [x] 閱讀《[The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/)》(LKMPG) 並解釋 [simrupt](https://github.com/sysprog21/simrupt) 程式碼裡頭的 mutex lock 的使用方式,並探討能否改寫為 [lock-free](https://hackmd.io/@sysprog/concurrency-lockfree); > 參照 [2021 年的筆記](https://hackmd.io/@linD026/simrupt-vwifi)。歡迎貢獻 LKMPG! > $\to$ 搭配閱讀〈[並行和多執行緒程式設計](https://hackmd.io/@sysprog/concurrency)〉 - [x] 探討 Timsort, Pattern Defeating Quicksort (pdqsort) 及 Linux 核心 [lib/sort.c](https://github.com/torvalds/linux/blob/master/lib/sort.c) 在排序過程中的平均比較次數,並提供對應的數學證明; > 對照 [fluxsort](https://github.com/scandum/fluxsort) 和 [crumsort](https://github.com/scandum/crumsort) 的分析和效能評比方式 - [x] 研讀 [CMWQ](https://www.kernel.org/doc/html/latest/core-api/workqueue.html) (Concurrency Managed Workqueue) 文件,對照 [simrupt](https://github.com/sysprog21/simrupt) 專案的執行表現,留意到 worker-pools 類型可指定 "Bound" 來分配及限制特定 worker 執行於指定的 CPU,Linux 核心如何做到?CMWQ 關聯的 worker thread 又如何與 CPU 排程器互動? > 搭配閱讀《Demystifying the Linux CPU Scheduler》 - [ ] 解釋 `xoroshiro128+` 的原理 (對照〈[Scrambled Linear Pseudorandom Number Generators](https://arxiv.org/pdf/1805.01407.pdf)〉論文),並利用 [ksort](https://github.com/sysprog21/ksort) 提供的 `xoro` 核心模組,比較 Linux 核心內建的 `/dev/random` 及 `/dev/urandom` 的速度,說明 `xoroshiro128+` 是否有速度的優勢?其弱點又是什麼? > $\to$ 搭配閱讀: [不亂的「亂數」](https://blog.cruciferslab.net/?p=599) - [ ] 解釋 [ksort](https://github.com/sysprog21/ksort) 如何運用 CMWQ 達到並行的排序; ## 處理並行排序的 Linux 核心模組的前期準備 根據 [M06: integration](https://hackmd.io/@sysprog/linux2024-integration-a#-ksort-%E8%99%95%E7%90%86%E4%B8%A6%E8%A1%8C%E6%8E%92%E5%BA%8F%E7%9A%84-Linux-%E6%A0%B8%E5%BF%83%E6%A8%A1%E7%B5%84) 的步驟準備。我的電腦上已經有 Linux 系統了。我們需要將 UEFI Secure Boot 的功能**關閉**。可以重新開機,選擇 UEFI 的選項,找到進階設定中的 Security ,把 Secure Boot 設置為 Disable 。 接著就可以按照步驟將 `ksort` 專案載入,但在使用 `make check` 命令編譯後,出現以下警告,是編譯器版本不同的問題,但尚未處理。 ``` warning: the compiler differs from the one used to build the kernel The kernel was built by: x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0 You are using: gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0 ``` 透過以下命令就可以將模組載入核心,使用 `insmod` 將模組載入核心後才會有下面的裝置檔案 `/dev/sort`。 ``` $ sudo insmod sort.ko ``` ### 閱讀〈Linux 核心模組運作原理〉 #### `insmod` 解釋 在解釋 `insmod` 命令之前,先來說明 Linux 核心模組(module) 到底是什麼? Linux 核心模組是可以動態載入到 Linux 核心而無需重新啟動的程式碼片段。它旨在擴展核心的功能,允許包含新功能或支援各種硬體設備或檔案系統。 核心模組允許在核心中新增或刪除程式碼,而無需重新編譯或修改整個核心。由於它們允許按需添加設備驅動程序,因此它們對於支援編譯核心時不可用的硬體特別有用。 有動態載當就就靜態載入。靜態載入的缺點就是有問題會很難處理,每次修改一個地方就要重新編譯核心並下載,導致效率不好。而若採用靜態載入的驅動程式較多,就會導致核心非常龐大,浪費資源。 核心模組儲存在副檔名為 `.ko` 的檔案中,而這個檔案是一個可執行的二進制文件,我們要透過 `insmod` 或 `modprobe` 命令將其載入到核心中,若是載入核心,就要在 kernel space 執行,需要將原本在 user space 中的文件透過 system call 的方式處理文件並將其複製到 kernel space 的記憶體空間中。一旦加載後,該模組就成為作業系統核心的一個組成部分,並且可以與其他系統元件互動。 ![linux-kernel-module-1](https://hackmd.io/_uploads/B1CXCE-DR.png) > 資料及圖片來源: [What is a Kernel Module in Linux?](https://www.scaler.com/topics/linux-kernel-module/) 以教材中的 Hello 程式來解釋。 ```cpp #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) { printk(KERN_INFO "Hello, world\n"); return 0; } static void hello_exit(void) { printk(KERN_INFO "Goodbye, cruel world\n"); } module_init(hello_init); module_exit(hello_exit); ``` * 包含核心模組開發所需的頭文件。 ```cpp #include <linux/init.h> #include <linux/module.h> ``` * 這些巨集定義模組的授權、作者身份和描述。在這種情況下,該模組是根據 GPL(GNU 通用公共授權)獲得許可。 ```cpp MODULE_LICENSE("Dual BSD/GPL"); ``` 根據 [GPL](https://zh.wikipedia.org/zh-tw/GNU%E9%80%9A%E7%94%A8%E5%85%AC%E5%85%B1%E8%AE%B8%E5%8F%AF%E8%AF%81) 可以知道 GPL(General Public License)是一個自由軟體授權條款,而從 [Linux kernel licensing rules](https://docs.kernel.org/process/license-rules.html) 得知有以下幾種條款。 1. `GPL`:GNU General Public License,通用公共許可證。 2. `GPL v2`:GNU General Public License version 2,第二版的通用公共許可證。 3. `GPL and additional rights`:表示模組除了遵守 GPL 還有額外的權利。 4. `Dual BSD/GPL`:表示模組同時遵守 BSD 和 GPL 許可證。 5. `Proprietary`:表示模組是專有軟件,不遵守 GPL。 * 該函數是模組的初始化例程。當使用 insmod 命令將模組載入到核心時會呼叫它。根據這篇 [Message logging with printk](https://www.kernel.org/doc/html/latest/translations/zh_CN/core-api/printk-basics.html) 可以知道 printk 是 Linux 核心中的一個函數,用於核心狀態消息打印和日誌記錄。類似於用戶態中的 printf 函數。`<loglevel>` 表示日誌級別,用於指示消息的嚴重程度。範例中的 `KERN_INFO` 就是信息性消息。 ```cpp static int hello_init(void) { printk(KERN_INFO "Hello, world\n"); return 0; } ``` * 該函數是模組的退出例程,當使用 `rmmod` 命令從核心卸載模組時呼叫函數。 ```cpp static void hello_exit(void) { printk(KERN_INFO "Goodbye, cruel world\n"); } ``` #### 使用 strace 工具追蹤 Linux 核心的掛載,涉及哪些系統呼叫和子系統? 輸入 `$ sudo strace insmod fibdrv.ko` 命令,即可以觀察式執行的 system call。 * 設置特定於體系結構的行程或執行緒狀態,如`arch_prctl`。 * `mmap` 將文件映射到記憶體空間上,`access` 檢查呼叫程序是否可以存取該文件路徑名。`mprotect` 更改呼叫的存取保護行程的記憶體區間。 * `openat` 打開模組文件,最後再用 `close` 關起來。 * `finit_module` 將可 ELF(Executable and Linking Format) 載入到 kernel space 並初始化模組。 ## 閱讀《[The Linux Kernel Module Programming Guide](https://sysprog21.github.io/lkmpg/)》(LKMPG) 並解釋 [simrupt](https://github.com/sysprog21/simrupt) 程式碼 ### 背景知識 [simrupt](https://github.com/sysprog21/simrupt) 模擬硬體中斷,並將模擬的中斷事件數據傳遞到 User space。而整體的操作流程如下: 1. 使用 `simrupt_init` 函式加載模組,初始化 FIFO 緩衝區並設置定時器。 2. 定時器周期性觸發,調用 `timer_handler` 。 3. 調用 `process_data` 函式生成模擬數據並將其放入快速緩衝區。 4. 任務從快速緩衝區中提取數據,並將其放入 FIFO 緩衝區。 5. User space 的程式通過讀取數據設備來獲取這些數據。 ### kfifo 值得注意的是, FIFO 緩衝區是使用定義在 [include/linux/kfifo.h](https://github.com/torvalds/linux/blob/master/include/linux/kfifo.h) 的 Linux API 。它是一個**環狀佇列**。並且在 One producer One Consumer(1P1C) 的情況下是可以無鎖。以下是 kfifo 的示意圖: ![Screenshot from 2024-07-04 21-28-45](https://hackmd.io/_uploads/HJ8sm74D0.png) 先看一下 kfifo 的結構體: ```cpp struct __kfifo { unsigned int in; unsigned int out; unsigned int mask; unsigned int esize; void *data; }; ``` kfifo 使用了 `in` 和 `out` 兩個變量分別作為加入和離開索引。而在最簡單的多執行緒,也就是 1P1C 的情況下,兩個執行緒做加入和離開時就不會發生同時存取到同一個變數,進而達成無鎖的狀況。 * 加入 `n` 個數據 `in` 就加 `n` * 離開 `m` 個數據 `out` 就加 `m` * `out` 不能比 `in` 大,若 `out` 等於 `in` 代表佇列為空 * `in` 不能比 `out` 大超過 FIFO 的空間 考慮到如果一直有數據加入,`in` 的數值會越來越大,但因為其是 `unsigned int` 型態,所以當 `in` 到 $2^{32}$ 後,就會自動變成 0 了。 這邊整理幾個 kfifo 的特色: 1. `in` 和 `out` 使用 `unsigned int` 型態,在加入和離開時不對其取餘數,而是讓其自然溢出,**讓其保持 `in - out` 永遠是佇列的大小**。 2. **保證佇列的大小為 2 的幂**,這樣做的目的是為了使加入和離開數據的讀取不用取餘數,而是利用 `mask` 變數去 AND 上 `in` 或 `out`,而 `mask` 本身就是佇列大小減一。根據 [lib/kfifo.c](https://elixir.bootlin.com/linux/v4.14/source/lib/kfifo.c#L109): ```cpp=109 off &= fifo->mask; ``` 3. 使用 [Memory barrier](https://zh.wikipedia.org/zh-tw/%E5%86%85%E5%AD%98%E5%B1%8F%E9%9A%9C) 來達成 1P1C 的情況下 kfifo 的無鎖並行(Concurrency)。以下是 kfifo 在加入和離開時用的幾個 Memory barrier 操作。 * `smp_rmb` 保證讀取操作之間不會出現亂序 * `smp_wmb` 保證寫入操作之間不會出現亂序 * `smp_mb` 保證讀寫操作都不會出現亂序 ### simrupt 專案解釋 本專案旨在模擬一個中斷的 Linux 設備驅動程式,並將資料傳回 User space。先上流程圖: ![simrupt](https://hackmd.io/_uploads/ryM9BYqw0.png) 這邊一步一步來解釋: #### Timer 使用計時器 `timer_handler` 來模擬週期性的 IRQ (Interrupt request) 。會發出中斷請求。 ```cpp /* Timer to simulate a periodic IRQ */ static struct timer_list timer; static void timer_handler(struct timer_list *__timer) { ktime_t tv_start, tv_end; s64 nsecs; pr_info("simrupt: [CPU#%d] enter %s\n", smp_processor_id(), __func__); /* We are using a kernel timer to simulate a hard-irq, so we must expect * to be in softirq context here. */ WARN_ON_ONCE(!in_softirq()); /* Disable interrupts for this CPU to simulate real interrupt context */ local_irq_disable(); tv_start = ktime_get(); process_data(); tv_end = ktime_get(); nsecs = (s64) ktime_to_ns(ktime_sub(tv_end, tv_start)); pr_info("simrupt: [CPU#%d] %s in_irq: %llu usec\n", smp_processor_id(), __func__, (unsigned long long) nsecs >> 10); mod_timer(&timer, jiffies + msecs_to_jiffies(delay)); local_irq_enable(); } ``` ### process_data 收到 Timer 的中斷信號後,`process_data` 函式呼叫`fast_buf_put(update_simrupt_data());` ,其中 `update_simrupt_data()` 會產生資料,這些資料的範圍在 0x20 到 0x7E 之間,即 ASCII 中的可顯示字元,這些資料會被放入 circular buffer 中,最後交由 `tasklet_schedule` 進行排程。 ### fast circular buffer Simrupt 在將產生的資料放入 kfifo 前,會先使用這個 「更快」的緩衝區來存放資料。 ```cpp /* We use an additional "faster" circular buffer to quickly store data from * interrupt context, before adding them to the kfifo. */ static struct circ_buf fast_buf; ``` fast circular buffer,顧名思義,是一個環狀的緩衝區,它用兩個變數 `head` 和 `tail` 來只是佇列的大小。當 `head` 和 `tail` 相同時代表緩衝區為空。 * `fast_buf_put` 扮演一個 producer 的角色,藉由 `CIRC_SPACE()` 判斷 buffer 中是否有剩餘空間,並更新 `head index`。它會將生成的資料放入 fast circular buffer 。 ```cpp static int fast_buf_put(unsigned char val) { struct circ_buf *ring = &fast_buf; unsigned long head = ring->head; /* prevent the compiler from merging or refetching accesses for tail */ unsigned long tail = READ_ONCE(ring->tail); /* is circular buffer full? */ if (unlikely(!CIRC_SPACE(head, tail, PAGE_SIZE))) return -ENOMEM; ring->buf[ring->head] = val; /* commit the item before incrementing the head */ smp_wmb(); /* update header pointer */ ring->head = (ring->head + 1) & (PAGE_SIZE - 1); return 0; } ``` * `fast_buf_get` 扮演一個 consumer 的角色,會從緩衝區中取得資料,並更新 `tail index`。它會將 fast circular buffer 的資料放入 work queue。 ```cpp static int fast_buf_get(void) { struct circ_buf *ring = &fast_buf; /* prevent the compiler from merging or refetching accesses for tail */ unsigned long head = READ_ONCE(ring->head), tail = ring->tail; int ret; if (unlikely(!CIRC_CNT(head, tail, PAGE_SIZE))) return -ENOENT; /* read index before reading contents at that index */ smp_rmb(); /* extract item from the buffer */ ret = ring->buf[tail]; /* finish reading descriptor before incrementing tail */ smp_mb(); /* increment the tail pointer */ ring->tail = (tail + 1) & (PAGE_SIZE - 1); return ret; } ``` #### tasklet 先來考慮一下 Linux 中斷的流程,大致分成兩個部份: 1. 中斷 handler (top half) 2. deferable task (bottom half) 對於中斷 handler (top half) 是中斷中緊急的部份,例如cpu與週邊裝置之間的交互,取得狀態,ack狀態,收發資料等等。而 deferable task 的工作則是相對不緊急的,例如後續的資料收發。 因此對於作業系統而言,我可以設計一個機制來將 top half 和 bottom half 的任務分離,盡可能加速 top half 的工作。而 tasklet 就是這樣的機制,用來推遲不重要的任務的執行。 tasklet 是基於 softirq 之上建立的,但最大的差別在於 tasklet 可以動態配置且可以被用在驅動裝置上。 使用 `queue_work` 將 work 放入 workqueue 中,並記錄執行時間。 ```cpp /** * queue_work - queue work on a workqueue * @wq: workqueue to use * @work: work to queue */ static inline bool queue_work(struct workqueue_struct *wq, struct work_struct *work) { return queue_work_on(WORK_CPU_UNBOUND, wq, work); } ``` #### workqueue 請參照下面筆記提到 workqueue 的運作原理。 這裡用了兩個 mutex_lock ,一個是 producer,一個是 comsumer 的鎖。producer 的鎖是用來防止多個任務寫入 kfifo ,comsumer 的鎖是用來阻止其他任務取得 circular buffer 的資料。 ```cpp /* Workqueue handler: executed by a kernel thread */ static void simrupt_work_func(struct work_struct *w) { int val, cpu; /* This code runs from a kernel thread, so softirqs and hard-irqs must * be enabled. */ WARN_ON_ONCE(in_softirq()); WARN_ON_ONCE(in_interrupt()); /* Pretend to simulate access to per-CPU data, disabling preemption * during the pr_info(). */ cpu = get_cpu(); pr_info("simrupt: [CPU#%d] %s\n", cpu, __func__); put_cpu(); while (1) { /* Consume data from the circular buffer */ mutex_lock(&consumer_lock); val = fast_buf_get(); mutex_unlock(&consumer_lock); if (val < 0) break; /* Store data to the kfifo buffer */ mutex_lock(&producer_lock); produce_data(val); mutex_unlock(&producer_lock); } wake_up_interruptible(&rx_wait); } ``` #### kfifo 使用 `produce_data` 將資料插入 kfifo 的緩衝區裡。 ```cpp /* Insert a value into the kfifo buffer */ static void produce_data(unsigned char val) { /* Implement a kind of circular FIFO here (skip oldest element if kfifo * buffer is full). */ unsigned int len = kfifo_in(&rx_fifo, &val, sizeof(val)); if (unlikely(len < sizeof(val)) && printk_ratelimit()) pr_warn("%s: %zu bytes dropped\n", __func__, sizeof(val) - len); pr_debug("simrupt: %s: in %u/%u bytes\n", __func__, len, kfifo_len(&rx_fifo)); } ``` 最後,使用 `kfifo_to_user` 將最多 len 個 bytes 資料從 fifo 移到 userspace。再透過 `simrupt_read` 從 user space 讀取資料。 ### mutex lock 改寫為 lock-free 從上面的說明可以知道在 1P1C 的情況下 kfifo 是無鎖並行的,因此不需要對 kfifo 處理,而是要處理快速緩衝區 `fast_buf` 要改為無鎖操作。 原本的 mutex lock 為一個行程進入臨界區,若是無法獲得鎖的情況下,會進入阻塞狀態。而這邊使用到的是 `mutex_lock_interruptible` 函式,其和一般的 mutex lock 的差別就是提供 mutex 在睡眠狀態下仍然能被中斷。 ```cpp ... if (mutex_lock_interruptible(&read_lock)) return -ERESTARTSYS; ... mutex_unlock(&read_lock); ``` 參考了[並行程式設計: Atomics 操作](https://hackmd.io/@sysprog/concurrency-atomics#wait-free-amp-lock-free)裡面提到: > mutex 導致效能下降的因素,往往是因臨界區過長且過於頻繁,也就限制並行程度,或競爭過於激烈 (context switch 開銷大幅增加)。lock-free/wait-free 演算法的價值在於,其保證一個或所有執行緒始終有進展,而非絕對的高效率。lock-free 和 wait-free 演算法在一種狀況會獲得更好的效率:演算法本身可用少量 atomic 指令實作 —— 實作 lock 也要用到 atomic 指令,當演算法本身用少數指令即可完成時,相比額外用 lock 當然就會有優勢。 因此我嘗試引入 `linux/atomic.h` 的原子操作來實現,但還未確定效能上是否有較 mutex lock 更優異。 ```cpp static int fast_buf_get(void) { struct circ_buf *ring = &fast_buf; unsigned long head = atomic_read(&ring->head); unsigned long tail = atomic_read(&ring->tail); int ret; if (CIRC_CNT(head, tail, PAGE_SIZE) == 0) return -ENOENT; smp_rmb(); ret = ring->buf[tail]; atomic_set(&ring->tail, (tail + 1) & (PAGE_SIZE - 1)); return ret; } ``` ## 探討 Timsort, pdqsort、list_sort 平均比較次數 本節要探討 Timsort、pdqsort、list_sort 的平均比較次數,Timsort 和 list_sort 我之前已經分析過了,而 pdqsort 則是結合了插入排序法(Insertion sort)、堆積排序法(Heap sort)以及快速排序法(Quick sort)。 * [我的 Timsort 筆記](https://hackmd.io/DMjzkMZrSjWBSMznxlSQtw?view#quiz1-%E6%B8%AC%E9%A9%972) * [我的 list_sort 筆記](https://hackmd.io/@Appmedia/Linux_kernel_list_sort) * [pdqsort 筆記](https://hackmd.io/@Chang-Chia-Chi/pdqsort) > 參考 Chang-Chia-Chi ### pdqsort Pattern Defeating Quicksort(pdqsort) 是一種多種理想排序的混合排序演算法,其核心思想就是判斷現在數組的狀況來選擇要使用的演算法。和快速排序法一樣是 unstable 的,而時間複雜度上,在最好的情況為 $O(n)$ ,而最差的情況則為 $O(nlogn)$ 。 * pdqsort 使用到的排序演算法 | Algorithm | Best case | Average case | Worst case | Stability | | -------------- | ---------- | ------------ | ---------- | --------- | | Insertion sort | $O(n)$ | $O(n^2)$ | $O(n^2)$ | Stable | | Quick sort | $O(nlogn)$ | $O(nlogn)$ | $O(n^2)$ | Unstable | | Heap sort | $O(nlogn)$ | $O(nlogn)$ | $O(nlogn)$ | Unstable | 從上表可以看出三個不同演算法的特性: 1. Insrtion sort 在數組大小較短時,通常會有非常好的表現。 2. Quick sort 在平均狀況下的時間複雜度為 $O(nlogn)$ ,但是一旦 `pivot` 的選擇不好時,就會退化到 $O(n^2)$ ,因此選擇一個好的 `pivot` 是快速排序法的重點。 > 這邊可以參考我之前做的[實驗](https://hackmd.io/DMjzkMZrSjWBSMznxlSQtw#%E9%81%BF%E5%85%8D%E6%9C%80%E5%B7%AE%E7%8B%80%E6%B3%81%E7%9A%84%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E5%AF%A6%E4%BD%9C) 3. Heap sort 則保證了就算在最差情況其時間複雜度仍有 $O(nlogn)$ 。 根據這三個演算法各自的優點,pdqsort 會根據不同情況使用不同演算法: * 當長度小於一定數值時,執行插入排序法,$O(n)$ * 在一般狀況時,執行快速排序法,$O(nlogn)$ * 若是發現快速排序法的效能不佳,則執行堆積排序法,$O(nlogn)$ ![pdqsort](https://hackmd.io/_uploads/SJ-y0bYv0.png) ### 實驗 我將 Timsort、[pdqsort](https://github.com/hankluo6/pdqsort/blob/main/pdqsort.c)、list_sort 三種排序演算法整理,來探討其比較次數及執行時間的差別。 > 原始碼請參照 [Appmedia06/sort-comparison](https://github.com/Appmedia06/sort-comparison) 實驗環境如本筆記開頭所示。克隆專案後,可以直接執行 `make` 命令編譯,便可以執行實驗程式: ``` $ ./main <sort type> <size> <rand size> ``` * <sort type>: * 0: `list_sort` * 1: `Timsort` * 2: `pdqsort` * <size>: 數列長度 * <rand size>: 數列中隨機數的數量 這邊說明一下,在做實驗前都有將快取的資料清除,而因為本程式包含生成數列的部份以及因為數列部份不一,因此 perf 效能工具不好量測,因此我在排序函式前後放置量測時間變數。 #### 數列長度 10000,完全隨機 | | list_sort | Timsort | pdqsort | | ------------- | --------- | -------- | -------- | | 比較次數(次) | 121006 | 131788 | 165964 | | 排序時間(sec) | 0.004242 | 0.004398 | 0.001908 | #### 數列長度 50000,完全隨機 | | list_sort | Timsort | pdqsort | | ------------- | --------- | -------- | -------- | | 比較次數(次) | 721306 | 770839 | 954597 | | 排序時間(sec) | 0.011339 | 0.010800 | 0.009194 | #### 數列長度 10000, 一半隨機 | | list_sort | Timsort | pdqsort | | ------------- | --------- | -------- | -------- | | 比較次數(次) | 98943 | 75600 | 125043 | | 排序時間(sec) | 0.002629 | 0.002530 | 0.001190 | #### 數列長度 50000, 一半隨機 | | list_sort | Timsort | pdqsort | | ------------- | --------- | -------- | -------- | | 比較次數(次) | 579353 | 451795 | 670515 | | 排序時間(sec) | 0.007334 | 0.006555 | 0.006200 | #### 數列長度 10000, 已排序(升序) | | list_sort | Timsort | pdqsort | | ------------- | --------- | -------- | -------- | | 比較次數(次) | 73777 | 21070 | 20008 | | 排序時間(sec) | 0.000972 | 0.000354 | 0.000103 | #### 數列長度 50000, 已排序(升序) | | list_sort | Timsort | pdqsort | | ------------- | --------- | -------- | -------- | | 比較次數(次) | 437120 | 105515 | 100008 | | 排序時間(sec) | 0.003127 | 0.001418 | 0.000532 | #### 視覺化 * 比較次數,數列長度 50000 ![compare](https://hackmd.io/_uploads/S1qsbVYwA.png) * 時間,數列長度 50000 ![time](https://hackmd.io/_uploads/rkEh-EKvR.png) ### 總結 探討 pdqsort ,作為混合排序法的演算法,可以看到當數列從亂序到已排序,比較次數明顯下降,便可以推斷出因為以排序了,導致快速排序法的效能不好,因此改為堆積排序法。 而 pdqsort 不管在哪個數列的情況都是贏過另外兩個演算法,也證明了混合各自演算法優點是一個好的決策。 這邊有一個有趣的發現,就是前面提到每次實驗都會先將快取清除才開始。Timsort 和 pdqsort 其實有沒有清掉快取較能並沒有插到非常多。但是 list_sort 若是沒有清掉快取效能速度竟然可以快了 $2.6$ 倍,這也證明了 list_sort 是對快取友善的。 ### 參考 > [Pattern-defeating Quicksort](https://arxiv.org/abs/2106.05123) > [Pattern-defeating Quicksort 閱讀筆記](https://hackmd.io/@Chang-Chia-Chi/pdqsort) > [打造 Go 语言最快的排序算法](https://blog.csdn.net/ByteDanceTech/article/details/124464192) > [hankluo6/pdqsort](https://github.com/hankluo6/pdqsort) ## 研讀 [CMWQ](https://www.kernel.org/doc/html/latest/core-api/workqueue.html) 文件,對照 [simrupt](https://github.com/sysprog21/simrupt) 專案的執行表現 > 可以參照以下筆記,寫的非常詳細 > [M06: integration](https://hackmd.io/@sysprog/linux2024-integration-c#M06-integration) ### Concurrency Managed Workqueue Linux 核心提供 workqueue 介面以達成非同步 (asynchronous) 的流程執行,使用者以一個 "work item" 來描述需被執行的 context,將其加入到所建立佇列之中,Linux 核心隨後會選出合適的 "worker" thread 來完成,處理結束者會從佇列中刪除,目的是要簡化執行緒的建立,可根據目前系統的 CPU 個數建立執行緒,使得執行緒得以並行。 而為了解決以前 workqueue 的問題: * 使用 ST workqueue 得到的並行等級較低,但較省資源。 * 使用 MT workqueue 固然有嚴重的消耗資源,能提高的並行等級卻很有限。 就有了 CMWQ 的產生,其具有以下特性: * 保持與原本的 workqueue API 的兼容性。 * 捨棄各個 workqueue 獨立對應一組 worker-pools 的作法。取而代之,所有 workqueue 會共享 per-CPU worker pools,並按需提供靈活的並行等級,避免大量的資源耗損 * worker pool 和並行等級間的調整由系統內部處理,對使用者來說可以視為一個黑盒子。 ### Bound & Unbound 考慮到 worker-pools 有兩種類型: 1. Bound worker-pools: 它會綁定特定的 CPU,使管理的 worker 執行在指定的 CPU 上執行,而每個 CPU 中會有二個 worker-pools 一個為高優先級的,另一個給普通優先級的。 2. Unbound worker-pools: thread pool 用於處理不綁定特定 CPU,其 thread pool 是動態變化,藉由設定 workqueue 的屬性建立對應的 worker-pools。

    Import from clipboard

    Paste your markdown or webpage here...

    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 has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a 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

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    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
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      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

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

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

        Syncing

        Push failed

        Push successfully