1. 程式人生 > >剖析 epoll ET/LT 觸發方式的效能差異誤解(定性分析)

剖析 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。

通常的誤區是: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 的效能瓶頸需要視應用場景而定,不可一概而論。

歡迎就此話題進行深入調研、討論!

參考資料:
• linux kernel source:fs/eventpoll.c
• “Comparing and Evaluating epoll, select, and poll Event
Mechanisms”:http://bcr2.uwaterloo.ca/~brecht/papers/getpaper.php?file=ols-2004.pdf
• “Edge-triggered interfaces are too difficult?”:http://lwn.net/Articles/25137/

By QingWu