# Epoll vs. io_uring 效能測試與比較 ###### tags: `linux2020` --- ## 測試環境 一台實體機 (Server),與一台遠端主機 (Client) 透過外部網路互連,並非直接網路線對接或只經過一台交換器 #### Server side (physical machine) - 作業系統 Ubuntu 20.04.1 LTS Linux kernel v5.8.0 - 硬體 CPU: Intel® Pentium CPU 4500 @ 3.50GHz × 2 Memory: 23.4 GiB - 網路 IP: 140.116.aaa.aaa #### Client side (Remote machine) - 網路 IP: 140.116.bbb.bbb ##### 隔離 CPU 的核 1. `$ sudo vim /etc/default/grub` 開啟 `/etc/default/grub` 2. Edit `GRUB_CMDLINE_LINUX_DEFAULT="quiet splash isolcpus=0"` 找到 `GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"`, 在該行空白後添加 `isolcpus=1`, 其中等號右邊為 CPU 核的 index,從 0 開始 3. `$ sudo update-grub` 保存退出後執行更新命令 `update-grub` ##### 透過 taskset 把行程安排給特定 CPU 核 1. Check process affinity `$taskset -p PID`: 得到十六進位的 bitmask,換算成二進位後每個 set bit 代表與該核具親合 `$taskset -cp PID`: 得到十進位的版本 2. Assign process to specific CPU `$taskset -p COREMASK PID` 或 `$taskset -cp CORELIST PID` --- ## 測試項目 (io_uring vs epoll) ### 1. 系統效能 Client side: `$ ab -n 100000 -c $CONNECTION [-k] http://140.116.aaa.aaa:8081/`, $CONNECTION 為同時連線數量 #### Request per second (io_uring / epoll) 針對每秒處理 request 數量的部份我們分為有 keep-alive 與沒有 keep-alive 參數來進行 [ab command output message info.](https://hackmd.io/@shanvia/SyOvJ1wCw) * **With keep-alive parameter** 使用 keep-alive 參數時我們針對總數皆為十萬次 request,但改變同時連線的數量來比較: - Number of fail 的部份表示當 server 偵測到錯誤而關閉連線的數量,修改前的系統發送 request 失敗的比例維持在 1% 上下,此錯誤會強制關閉與 client 的連線,但修改後的系統錯誤率皆為 0% - Request per second 數量的比較上,修改後的數量略大於修改前 | Number of connection | Number of fail | Requests per second | Time per request (ms 1 conncurrent) | | :------------------ | :------------ | :-------------------------------------- |:----------------------------------- | | 100 | 0 / 1300 | 2266 / 2025 | 44 / 49 | | 300 | 0 / 1200 | 6799 / 6116 | 44 / 49 | | 500 | 0 / 1000 | 11336 / 10364 | 44 / 48 | | 800 | 0 / 800 | 17517 / 16680 | 46 / 48 | | 1000 | 0 / 1000 | 22522 / 20382 | 44 / 49 | ![](https://i.imgur.com/7iWUVRY.png) * **==Without== keep-alive parameter** - 在不需要與 client 保持連線的情況下,兩種 server 每秒處理的 request 皆大幅增加,且因為不用保持連線再度 read 而導致兩者的 number of fail 都降為 0% - 與上圖比較可發現 io_uring 方法的速度會隨著連線數量增加而遞減,原因是因為 io_uring 的作法會優先處理 accept 的 request,然後才處理 read, write;但 epoll 則是平均處理,因此不太會變動。 | Number of connection | Number of fail | Requests per second | Time per request (ms 1 conncurrent) | | :------------------ | :------------ | :-------------------------------------- |:----------------------------------- | | 100 | 0 / 0 | 58642 / 44147 | 2 / 2 | | 300 | 0 / 0 | 57408 / 40492 | 5 / 7 | | 500 | 0 / 0 | 55248 / 42380 | 9 / 12 | | 800 | 0 / 0 | 54008 / 42382 | 15 / 19 | | 1000 | 0 / 0 | 52506 / 42158 | 19 / 24 | ![](https://i.imgur.com/Rp4dTJZ.png) #### do_request 函式處理時間 [Plot time consumpsion of do_request function](https://hackmd.io/@shanvia/BkUZSJvRD) 我們嘗試測量系統處理每一次 request 所需的時間,發現修改後的程式碼時間分布較集中且少 ![](https://i.imgur.com/E9EtJKY.png) :::warning 嘗試計算十萬次 request 的時間並取其 99% 信賴區間後的圖形 ![](https://i.imgur.com/ZxpZtxj.png) ::: ### 2. CPU 使用情形 透過 `perf_events` 進行測試 Server side: `$ sudo perf stat -r 5 ./sehttpd > /dev/null` Client side: `$ ab -n 100000 -c 1000 [-k] http://140.116.aaa.aaa:8081/` 在這次的實作除了將 epoll 修改成 io_uring,也進行了一些修正: - 譬如透過 calloc() 函式實作 memory pool,取代原本因到處進行動態記憶體規劃而導致記憶體區段的碎片化,減少系統在運行時發生 `page-faults` 的次數。 - 另外因為我們修改了程式碼中的 switch case 的寫法,改以 `computed goto` 取代,減少 `branch-misses` 的發生 - 整體來看經過這上述修改及以 io_uring 實作,減少程式執行所須的 `cycles` 數及 `instructions` 數。 目前的僅有一台實體機作為 server 的數據,預期會再找其他實體機多測試。 * With keep-alive parameter + io_uring ``` Performance counter stats for './sehttpd' (5 runs): 1,533.19 msec task-clock # 0.113 CPUs utilized ( +- 0.57% ) 1,593 context-switches # 0.001 M/sec ( +- 0.87% ) 0 cpu-migrations # 0.000 K/sec 1,391 page-faults # 0.907 K/sec ( +- 0.03% ) 5,316,634,475 cycles # 3.468 GHz ( +- 0.57% ) 5,397,118,059 instructions # 1.02 insn per cycle ( +- 0.36% ) 1,031,652,823 branches # 672.880 M/sec ( +- 0.35% ) 11,219,837 branch-misses # 1.09% of all branches ( +- 0.65% ) 13.63 +- 4.94 seconds time elapsed ( +- 36.26% ) ``` + epoll ``` Performance counter stats for './sehttpd' (5 runs): 2,318.17 msec task-clock # 0.221 CPUs utilized ( +- 0.54% ) 876 context-switches # 0.378 K/sec ( +- 4.54% ) 0 cpu-migrations # 0.000 K/sec 101,220 page-faults # 0.044 M/sec ( +- 0.01% ) 8,046,643,190 cycles # 3.471 GHz ( +- 0.55% ) 6,151,488,232 instructions # 0.76 insn per cycle ( +- 0.25% ) 1,174,246,810 branches # 506.541 M/sec ( +- 0.24% ) 14,519,502 branch-misses # 1.24% of all branches ( +- 0.41% ) 10.503 +- 0.928 seconds time elapsed ( +- 8.83% ) ``` * ==Without== keep-alive + io_uring ``` Performance counter stats for './sehttpd' (5 runs): 2,931.98 msec task-clock # 0.371 CPUs utilized ( +- 0.36% ) 627 context-switches # 0.214 K/sec ( +- 0.86% ) 0 cpu-migrations # 0.000 K/sec 1,134 page-faults # 0.387 K/sec ( +- 4.71% ) 10,222,300,431 cycles # 3.486 GHz ( +- 0.36% ) 9,501,551,090 instructions # 0.93 insn per cycle ( +- 0.03% ) 1,792,242,173 branches # 611.274 M/sec ( +- 0.03% ) 21,889,467 branch-misses # 1.22% of all branches ( +- 0.71% ) 7.90 +- 1.32 seconds time elapsed ( +- 16.74% ) ``` + epoll ``` Performance counter stats for './sehttpd' (5 runs): 3,658.49 msec task-clock # 0.406 CPUs utilized ( +- 0.56% ) 774 context-switches # 0.212 K/sec ( +- 1.97% ) 0 cpu-migrations # 0.000 K/sec 102,289 page-faults # 0.028 M/sec ( +- 0.27% ) 12,713,996,125 cycles # 3.475 GHz ( +- 0.40% ) 10,128,855,709 instructions # 0.80 insn per cycle ( +- 0.02% ) 1,923,276,791 branches # 525.702 M/sec ( +- 0.01% ) 25,670,694 branch-misses # 1.33% of all branches ( +- 0.39% ) 9.012 +- 0.493 seconds time elapsed ( +- 5.47% ) --- ## Reference 1. [在 Linux 中以特定的 CPU 核心執行程式](https://blog.gtwang.org/linux/run-program-process-specific-cpu-cores-linux/) 2. [使用 perf_events 分析程式效能](https://zh-blog.logan.tw/2019/07/10/analyze-program-performance-with-perf-events/) 3. [io_uring echo server benchmarks](https://github.com/frevib/io_uring-echo-server/blob/415cc5046c2e1469c8d26ad774b568efb5495145/benchmarks/benchmarks.md)