io_uring vs epoll ,谁在网络编程领域更胜一筹?
簡介:從定量分析的角度,通過量化 io_uring 和 epoll 兩種編程框架下的相關(guān)操作的耗時,來分析二者的性能差異。
本文作者:王小光,「高性能存儲技術(shù)SIG」核心成員。
背景
io_uring 在傳統(tǒng)存儲 io 場景已經(jīng)證明其價值,但 io_uring 不僅支持傳統(tǒng)存儲 io,也支持網(wǎng)絡(luò) io。io_uring 社區(qū)有眾多的開發(fā)者嘗試將 io_uring 用于網(wǎng)絡(luò)應(yīng)用。我們之前也在《你認(rèn)為 io_uring 只適用于存儲 IO?大錯特錯!》中也探索過 io_uring 在網(wǎng)絡(luò)場景的應(yīng)用及其與傳統(tǒng)網(wǎng)絡(luò)編程基石 epoll 的對比,當(dāng)時我們的測試結(jié)果顯示在 cpu 漏洞緩解使能的前提下,io_uring 相比于 epoll 可以帶來一定的優(yōu)勢,在 cpu 漏銅緩解未使能時,io_uring 相比于 epoll 沒有優(yōu)勢,可能還會存在性能下降。
在 io_uring 社區(qū),關(guān)于 io_uring 和 epoll 孰優(yōu)孰劣也一直存在爭論,有些開發(fā)者宣稱 io_uring 可以獲得比 epoll 更好的性能,有些開發(fā)者則宣稱二者性能持平或者 io_uring 甚至不如 epoll。相關(guān)的討論非常多,具體可參見如下兩例:
https://github.com/axboe/liburing/issues/189
Wild results, cannot reproduce · Issue #8 · frevib/io_uring-echo-server · GitHub
以上討論從 2020 年 8 月一直持續(xù)到現(xiàn)在,其過程非常長也非常地激烈。可以看出 io_uring 和 epoll 在網(wǎng)絡(luò)編程領(lǐng)域孰優(yōu)孰劣目前確實比較難以達(dá)成共識。
目前很多業(yè)務(wù)想將 io_uring 在網(wǎng)絡(luò)場景應(yīng)用起來,但 io_uring 是否能比 epoll 帶來性能提升,大家或多或少存在些許疑問。為了徹底厘清這個問題,龍蜥社區(qū)高性能存儲 SIG嘗試從定量分析的角度,通過量化 io_uring 和 epoll 兩種編程框架下的相關(guān)操作的耗時,來分析二者的性能差異。
評估模型
我們?nèi)匀贿x用 echo server 模型進(jìn)行性能評估,server 端采用單線程模型,同時為公平對比,io_uring 不使用內(nèi)部的 io-wq 機(jī)制(io_uring 在內(nèi)核態(tài)維護(hù)的線程池,可以用來執(zhí)行用戶提交的 io 請求)。epoll 采用 send(2) 和 recv(2) 進(jìn)行數(shù)據(jù)的讀寫操作;而 io_uring 采用 IORING_OP_SEND 和 IORING_OP_RECV 進(jìn)行數(shù)據(jù)的讀寫操作。
結(jié)合 echo server 的模型,我們分析有四個因素會影響 io_uring 和 epoll 的性能,分別是:
1、系統(tǒng)調(diào)用用戶態(tài)到內(nèi)核態(tài)上下文切換開銷,記為 s;
2、系統(tǒng)調(diào)用自身內(nèi)核態(tài)工作邏輯開銷,記為 w;
3、io_uring 框架本身開銷,記為 o;
4、io_uring 的 batch 量,記為 n,epoll 版 echo server 由于直接調(diào)用 recv(2) 和 send(2), 其 batch 實際為 1。
同時在本文中我們僅評估 io_uring 和 epoll 請求讀寫操作的開銷,對于 io_uring 和 epoll 本身的事件通知機(jī)制本身不做衡量,因為通過 perf 工具分析,讀寫請求本身開銷占據(jù)絕大部分。系統(tǒng)調(diào)用用戶態(tài)到內(nèi)核態(tài)上下文切換開銷可以通過專門的程序進(jìn)行測量,因素 2、3、4 等可以通過衡量內(nèi)核相關(guān)函數(shù)的執(zhí)行時間進(jìn)行測量,用 bpftrace 進(jìn)行分析。
epoll 版 echo server 開銷度量
從用戶態(tài)視角,send(2) 或者 recv(2) 開銷主要包含兩個方面,系統(tǒng)調(diào)用用戶態(tài)到內(nèi)核態(tài)上下文切換開銷和系統(tǒng)調(diào)用自身內(nèi)核態(tài)工作邏輯開銷,其中系統(tǒng)調(diào)用本身工作邏輯的開銷,send(2) 和 recv(2) 分別衡量 sys_sendto(), sys_recvfrom() 即可。
由于 epoll 場景下其系統(tǒng)調(diào)用的 batch 為 1,因此 epoll ?模型下收發(fā)請求的平均耗時為 (s + w)。
io_uring 版 echo server 開銷度量
io_uring 中 io_uring_enter(2) 系統(tǒng)調(diào)用既可以用來提交 sqe,也可以用來 reap cqe,兩種操作混合在一個系統(tǒng)調(diào)用中,準(zhǔn)確衡量 sqe 的提交收發(fā)請求的耗時比較困難。簡單起見,我們采用跟蹤 io_submit_sqes() 的開銷來衡量 IORING_OP_SEND 和 IORING_OP_RECV 的開銷,此函數(shù)被 io_uring_enter(2) 所調(diào)用。io_submit_sqes() 包含send(2) 和 revc(2) 內(nèi)核側(cè)工作邏輯開銷,及 io_uring 框架的開銷,記為 t。
同時我們采用 io_uring 的 multi-shot 模式,從而確保 io_submit_sqes() 中的提交的 IORING_OP_SEND 和 IORING_OP_RECV 請求都可以直接完成,而不會用到io_uring的 task-work 機(jī)制。
由于 io_uring 場景下可以 batch 系統(tǒng)調(diào)用的執(zhí)行,因此 io_uirng 模型下收發(fā)請求的平均耗時為 (s + t) / n。
實際度量
我們測試環(huán)境 Intel(R) Xeon(R) CPU E5-2682 v4 @ 2.50GHz,衡量 echo server 單鏈接性能數(shù)據(jù)。
用戶態(tài)內(nèi)核態(tài)系統(tǒng)調(diào)用上下文切換開銷
cpu 漏洞對系統(tǒng)調(diào)用用戶態(tài)內(nèi)核態(tài)上下文切換的影響比較大,在我們的測試環(huán)境中:漏銅緩解使能時,系統(tǒng)調(diào)用的上下文切換開銷為 700ns 左右;漏銅緩解未使能時,系統(tǒng)調(diào)用的上下文切換開銷為 230ns 左右。
epoll 模型下 send(2)/recv(2) 內(nèi)核側(cè)開銷
采用bpftrace 腳本分別衡量 sys_sendto(),sys_recvfrom() 即可。 bpftrace 腳本如下:
BEGIN {@start = 0;@send_time = 0;@send_count = 0; } kprobe:__sys_sendto /comm == "epoll_echo_serv"/ {@start = nsecs; } kprobe:__sys_recvfrom /comm == "epoll_echo_serv"/ {@start = nsecs; } kretprobe:__sys_sendto /comm == "epoll_echo_serv"/ { if (@start > 0) {@delay = nsecs - @start;@send_time = @delay + @send_time;@send_count = @send_count + 1;} } kretprobe:__sys_recvfrom /comm == "epoll_echo_serv"/ { if (@start > 0) {@delay = nsecs - @start;@send_time = @delay + @send_time;@send_count = @send_count + 1;} } interval:s:5 { printf("time: %llu\n", @send_time / @send_count);@send_time = 0;@send_count = 0; }在單連接,包大小 16 字節(jié)場景下,epoll 版的 echo_server 的 tps 在 1000 左右,其 recv(2) 和 send(2) 的內(nèi)核側(cè)邏輯平均開銷如下:
time: 1489、time: 1492、time: 1484、time: 1491、time: 1499、time: 1505、time: 1512、time: 1528、time: 1493、time: 1509、time: 1495、time: 1499、time: 1544
從上述數(shù)據(jù)可以看出,send(2) 和 recv(2) 的內(nèi)核側(cè)平均開銷在1500ns左右,因此:
1) cpu 漏洞緩解,send(2) 和 recv(2) 的平均開銷為 s=700ns,w=1500ns,總共 (s+w) = 2200ns。
2) cpu 漏洞為緩解,send(2) 和 recv(2) 的平均開銷為 s=230ns,w=1500ns,總共 (s+w) = 1730ns。
io_uring 模型下 io_uring_enter(2) 內(nèi)核側(cè)開銷
采用bpftrace 腳本分別衡量 io_submit_sqes() 開銷即可。
BEGIN {@start = 0;@send_time = 0;@send_count = 0; } kprobe:io_submit_sqes /comm == "io_uring_echo_s"/ {@start = nsecs;@send_count = @send_count + arg1; } kretprobe:io_submit_sqes /comm == "io_uring_echo_s"/ { if (@start > 0) {@delay = nsecs - @start;@send_time = @delay + @send_time;} } interval:s:5 { printf("time: %llu\n", @send_time / @send_count);@send_time = 0;@send_count = 0; }運(yùn)行類似上述 epoll 同樣的測試,數(shù)據(jù)為:
time: 1892、time: 1901、time: 1901、time: 1882、time: 1890、time: 1936、time: 1960、time: 1907、time: 1896、time: 1897、time: 1911、time: 1897、time: 1891、time: 1893、time: 1918、time: 1895、time: 1885
從上述數(shù)據(jù)可以看出,io_submit_sqes() 的內(nèi)核側(cè)平均開銷在 1900ns 左右,注意此時的batch n=1,且該開銷包括收發(fā)請求的內(nèi)核態(tài)工作邏輯開銷及 io_uring 框架開銷。
1) cpu 漏洞緩解,用戶態(tài)觀察到的 io_uring_enter(2) 平均開銷為 t=1900ns,n=1,s=700ns,總共 (t+s) / n = 2600ns。
2) cpu 漏洞未緩解,用戶態(tài)觀察到的 io_uring_enter(2) 的平均開銷為 t=1900ns,n=1,s=230ns,總共 (t+s) / n = 2130ns。
注意由于我們實際只 trace io_submit_sqes,而 io_uring_enter(2) 系統(tǒng)調(diào)用是調(diào)用 io_submit_sqes 的,因此 io_uring_enter(2) 的實際開銷是肯定大于 (t+s) / n。
數(shù)據(jù)量化分析
從上述數(shù)據(jù)發(fā)現(xiàn),cpu 漏洞確實對系統(tǒng)調(diào)用的性能影響較大,尤其對于小數(shù)據(jù)包的場景,我們分別討論下:
cpu 漏洞緩解未使能
epoll: s+w, ?io_uring: (t+s) / n
可以看出在此種情況下,由于 t 大于 w, 即使擴(kuò)大 batch,io_uring 的性能也不如 epoll。
cpu 漏洞緩解使能
epoll: s+w, ?io_uring: (t+s) / n
可以看出在此種情況下,由于 s 比較大,當(dāng) batch 比較低時,io_uring 不如 epoll,但當(dāng) batch 比較大時,io_uring 場景下系統(tǒng)調(diào)用上下文切換開銷被極大攤薄,此時 io_uring 的性能是優(yōu)于 epoll。在我們的實際測試中,1000連接時,io_uring 的的吞吐要比 epoll 高 10% 左右,基本符合我們的建模。
結(jié)論
從我們的量化分析可以看出 io_uring 與 epoll 孰優(yōu)孰劣完全由評估模型中定義的 4 個變量決定:
epoll: s + w
io_uring: (t + s) / n
如果某個變量占主導(dǎo)地位,則性能數(shù)據(jù)會截然不同。舉個例子,假設(shè)系統(tǒng)調(diào)用上下文切換開銷 s 很大,而且 io_uring batch n 也很大,則 io_uring 在此種場景下的性能肯定是會比 epoll 好;再比如系統(tǒng)內(nèi)核側(cè)開銷 w 很大,此時 io_uring 和 epoll 性能會接近。
因此 io_uring 和 epoll 孰優(yōu)孰劣,取決于其應(yīng)用場景,我們建議的最佳實踐是基于業(yè)務(wù)真實網(wǎng)絡(luò)模型,將其簡化為 echo server 模型,運(yùn)行我們的度量腳本,從而可以評估二者在真實環(huán)境的性能,以指導(dǎo)真實應(yīng)用開發(fā)。同時上述度量數(shù)據(jù)也為我們的性能優(yōu)化提供方向,我們可以盡可能的減少某一變量的開銷,從而提高性能,比如可以進(jìn)一步優(yōu)化 io_uring 框架的開銷。
高性能存儲技術(shù)SIG介紹
高性能存儲技術(shù)SIG :致力于存儲棧性能挖掘化,打造標(biāo)準(zhǔn)的高性能存儲技術(shù)軟件棧,推動軟硬件協(xié)同發(fā)展。
原文鏈接
本文為阿里云原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。?
總結(jié)
以上是生活随笔為你收集整理的io_uring vs epoll ,谁在网络编程领域更胜一筹?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 好云推荐官丨飞天加速之星怎样选择云服务器
- 下一篇: 「技术人生」第6篇:技术同学应该如何理解