今天看公司代碼時,發現代碼里面都是使用epoll,有的備注 epoll 效率比poll 高!!!
所以就得說一說了;宏觀看一看epoll 和select的實現:
select原理概述
調用select時,會發生以下事情:
- 從用戶空間拷貝fd_set到內核空間;
- 注冊回調函數__pollwait;
- 遍歷所有fd,對全部指定設備做一次poll(這里的poll是一個文件操作,它有兩個參數,一個是文件fd本身,一個是當設備尚未就緒時調用的回調函數__pollwait,這個函數把設備自己特有的等待隊列傳給內核,讓內核把當前的進程掛載到其中);
- 當設備就緒時,設備就會喚醒在自己特有等待隊列中的【所有】節點,於是當前進程就獲取到了完成的信號。poll文件操作返回的是一組標准的掩碼,其中的各個位指示當前的不同的就緒狀態(全0為沒有任何事件觸發),根據mask可對fd_set賦值;
- 如果所有設備返回的掩碼都沒有顯示任何的事件觸發,就去掉回調函數的函數指針,進入有限時的睡眠狀態,再恢復和不斷做poll,再作有限時的睡眠,直到其中一個設備有事件觸發為止。
- 只要有事件觸發,系統調用返回,將fd_set從內核空間拷貝到用戶空間,回到用戶態,用戶就可以對相關的fd作進一步的讀或者寫操作了。
epoll原理概述
調用epoll_create時,做了以下事情:
- 內核幫我們在epoll文件系統里建了個file結點;
- 在內核cache里建了個紅黑樹用於存儲以后epoll_ctl傳來的socket;
- 建立一個list鏈表,用於存儲准備就緒的事件。
調用epoll_ctl時,做了以下事情:
- 把socket放到epoll文件系統里file對象對應的紅黑樹上;
- 給內核中斷處理程序注冊一個回調函數,告訴內核,如果這個句柄的中斷到了,就把它放到准備就緒list鏈表里。
調用epoll_wait時,做了以下事情:
觀察list鏈表里有沒有數據。有數據就返回,沒有數據就sleep,等到timeout時間到后即使鏈表沒數據也返回。而且,通常情況下即使我們要監控百萬計的句柄,大多一次也只返回很少量的准備就緒句柄而已,所以,epoll_wait僅需要從內核態copy少量的句柄到用戶態而已。
select缺點:
- 最大並發數限制:使用32個整數的32位,即32*32=1024來標識fd,雖然可修改,但是有以下第二點的瓶頸;
- 效率低:每次都會線性掃描整個fd_set,集合越大速度越慢;
- 內核/用戶空間內存拷貝問題。
epoll的提升:
- 本身沒有最大並發連接的限制,僅受系統中進程能打開的最大文件數目限制;
- 效率提升:只有活躍的socket才會主動的去調用callback函數;
- 省去不必要的內存拷貝:epoll通過內核與用戶空間mmap同一塊內存實現。
假設現在有1024個fd ; select 和epoll 都同時維護他, 假設這些fd 都是活躍的, 這種情況下,select一次掃描 可以掃描1024個fd,空閑的fd很少,
但是epoll 就有可能不一樣了, epoll 是先注冊等待回調, 有可能出現1024次回調;
這樣的情況下, 要是說epoll 效率比select 高-----這就不好說了!!!!!!!!
如果select 和epoll 同時維護1024個fd ,但是每次只有一個fd有事件,這種情況下 select 每次都會掃描所有的fd, 對比於epoll 每次只有一個fd 回調。 select 做了很多無用功, 此時應該epoll的效率高吧!!
或者在短連接多的時候, 一個連接使用epoll 會觸發epoll_ctrl_add/del 兩次系統調用,但是select 只有一次掃描 ,此時 也許select 效率性能更高。
高並發,且任一時間只有少數socket是活躍的。如果在並發量低,socket都比較活躍的情況下,select就不見得比epoll慢了
所以 I/O復用模型 要根據業務需求來選擇,技術只有合適的技術-----------------------
終於找到這張神圖了
The benchmark measures how long it takes to serve one hundred active connections that chain writes to new connections until thousand writes and reads have happened. It exercises the event loop several times.