# 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)