剖析 epoll ET/LT 觸發方式的性能差異誤解(定性分析)


平時大家使用 epoll 時都知道其事件觸發模式有默認的 level-trigger 模式和通過 EPOLLET 啓用的 edge-trigger 模式兩種。從 epoll 發展歷史來看,它剛誕生時只有 edge-trigger 模式,後來因容易產生 race-cond 且不易被開發者理解,又增加了 level-trigger 模式並作爲默認處理方式。

二者的差異在於 level-trigger 模式下只要某個 fd 處於 readable/writable 狀態,無論什麼時候進行 epoll_wait 都會返回該 fd;而 edge-trigger 模式下只有某個 fd 從 unreadable 變爲 readable 或從 unwritable 變爲 writable 時,epoll_wait 纔會返回該 fd。(注:使用ET模式,讀寫時一定要考慮到返回EAGIN的情況,取完所有數據或者寫滿緩衝區。當有新數據到達或者數據傳送使發送緩衝區有空間可寫,就會觸發EPOLLIN/EPOLLOUT)

通常的誤區是:level-trigger 模式在 epoll 池中存在大量 fd 時效率要顯著低於 edge-trigger 模式。

但從 kernel 代碼來看,edge-trigger/level-trigger 模式的處理邏輯幾乎完全相同,差別僅在於 level-trigger 模式在 event 發生時不會將其從 ready list 中移除,略爲增大了 event 處理過程中 kernel space 中記錄數據的大小。

然而,edge-trigger 模式一定要配合 user app 中的 ready list 結構,以便收集已出現 event 的 fd,再通過 round-robin 方式挨個處理,以此避免通信數據量很大時出現忙於處理熱點 fd 而導致非熱點 fd 餓死的現象。統觀 kernel 和 user space,由於 user app 中 ready list 的實現千奇百怪,不一定都經過仔細的推敲優化,因此 edge-trigger 的總內存開銷往往還大於 level-trigger 的開銷。

一般號稱 edge-trigger 模式的優勢在於能夠減少 epoll 相關係統調用,這話不假,但 user app 裏可不是隻有 epoll 相關係統調用吧?爲了繞過餓死問題,edge-trigger 模式的 user app 要自行進行 read/write 循環處理,這其中增加的系統調用和減少的 epoll 系統調用加起來,有誰能說一定就能明顯地快起來呢?

實際上,epoll_wait 的效率是 O(ready fd num) 級別的,因此 edge-trigger 模式的真正優勢在於減少了每次 epoll_wait 可能需要返回的 fd 數量,在併發 event 數量極多的情況下能加快 epoll_wait 的處理速度,但別忘了這只是針對 epoll 體系自己而言的提升,與此同時 user app 需要增加複雜的邏輯、花費更多的 cpu/mem 與其配合工作,總體性能收益究竟如何?只有實際測量才知道,無法一概而論。不過,爲了降低處理邏輯複雜度,常用的事件處理庫大部分都選擇了 level-trigger 模式(如 libevent、boost::asio等)

結論:
• epoll 的 edge-trigger 和 level-trigger 模式處理邏輯差異極小,性能測試結果表明常規應用場景 中二者性能差異可以忽略。
• 使用 edge-trigger 的 user app 比使用 level-trigger 的邏輯複雜,出錯概率更高。
• edge-trigger 和 level-trigger 的性能差異主要在於 epoll_wait 系統調用的處理速度,是否是 user app 的性能瓶頸需要視應用場景而定,不可一概而論。


發佈了22 篇原創文章 · 獲贊 54 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章