select、poll、epoll三者的區別

(1)select==>時間複雜度O(n)

它僅僅知道了,有I/O事件發生了,卻並不知道是哪那幾個流(可能有一個,多個,甚至全部),我們只能無差別輪詢所有流,找出能讀出數據,或者寫入數據的流,對他們進行操作。所以select具有O(n)的無差別輪詢複雜度,同時處理的流越多,無差別輪詢時間就越長。

(2)poll==>時間複雜度O(n)

poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然後查詢每個fd對應的設備狀態, 但是它沒有最大連接數的限制,原因是它是基於鏈表來存儲的.

(3)epoll==>時間複雜度O(1)

epoll可以理解爲event poll,不同於忙輪詢和無差別輪詢,epoll會把哪個流發生了怎樣的I/O事件通知我們。所以我們說epoll實際上是事件驅動(每個事件關聯上fd)的,此時我們對這些流的操作都是有意義的。(複雜度降低到了O(1))

select,poll,epoll都是IO多路複用的機制。I/O多路複用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因爲他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現會負責把數據從內核拷貝到用戶空間。  

epoll跟select都能提供多路I/O複用的解決方案。在現在的Linux內核裏有都能夠支持,其中epoll是Linux所特有,而select則應該是POSIX所規定,一般操作系統均有實現

select:

select本質上是通過設置或者檢查存放fd標誌位的數據結構來進行下一步處理。這樣所帶來的缺點是:

1、 單個進程可監視的fd數量被限制,即能監聽端口的大小有限。

      一般來說這個數目和系統內存關係很大,具體數目可以cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.

2、 對socket進行掃描時是線性掃描,即採用輪詢的方法,效率較低:

       當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll與kqueue做的。

3、需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時複製開銷大

poll:

poll本質上和select沒有區別,它將用戶傳入的數組拷貝到內核空間,然後查詢每個fd對應的設備狀態,如果設備就緒則在設備等待隊列中加入一項並繼續遍歷,如果遍歷完所有fd後沒有發現就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒後它又要再次遍歷fd。這個過程經歷了多次無謂的遍歷。

它沒有最大連接數的限制,原因是它是基於鏈表來存儲的,但是同樣有一個缺點:

1、大量的fd的數組被整體複製於用戶態和內核地址空間之間,而不管這樣的複製是不是有意義。                   

2、poll還有一個特點是“水平觸發”,如果報告了fd後,沒有被處理,那麼下次poll時會再次報告該fd。

epoll:

epoll有EPOLLLT和EPOLLET兩種觸發模式,LT是默認的模式,ET是“高速”模式。LT模式下,只要這個fd還有數據可讀,每次 epoll_wait都會返回它的事件,提醒用戶程序去操作,而在ET(邊緣觸發)模式中,它只會提示一次,直到下次再有數據流入之前都不會再提示了,無 論fd中是否還有數據可讀。所以在ET模式下,read一個fd的時候一定要把它的buffer讀光,也就是說一直讀到read的返回值小於請求值,或者 遇到EAGAIN錯誤。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl註冊fd,一旦該fd就緒,內核就會採用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知。

epoll爲什麼要有EPOLLET觸發模式?

如果採用EPOLLLT模式的話,系統中一旦有大量你不需要讀寫的就緒文件描述符,它們每次調用epoll_wait都會返回,這樣會大大降低處理程序檢索自己關心的就緒文件描述符的效率.。而採用EPOLLET這種邊沿觸發模式的話,當被監控的文件描述符上有可讀寫事件發生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數據全部讀寫完(如讀寫緩衝區太小),那麼下次調用epoll_wait()時,它不會通知你,也就是它只會通知你一次,直到該文件描述符上出現第二次可讀寫事件纔會通知你!!!這種模式比水平觸發效率高,系統不會充斥大量你不關心的就緒文件描述符

epoll的優點:

1、沒有最大併發連接的限制,能打開的FD的上限遠大於1024(1G的內存上能監聽約10萬個端口)
2、效率提升,不是輪詢的方式,不會隨着FD數目的增加效率下降。只有活躍可用的FD纔會調用callback函數;
即Epoll最大的優點就在於它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高於select和poll。

3、 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少複製開銷。
select、poll、epoll 區別總結:

1、支持一個進程所能打開的最大連接數

select

單個進程所能打開的最大連接數有FD_SETSIZE宏定義,其大小是32個整數的大小(在32位的機器上,大小就是3232,同理64位機器上FD_SETSIZE爲3264),當然我們可以對進行修改,然後重新編譯內核,但是性能可能會受到影響,這需要進一步的測試。

poll

poll本質上和select沒有區別,但是它沒有最大連接數的限制,原因是它是基於鏈表來存儲的

epoll

雖然連接數有上限,但是很大,1G內存的機器上可以打開10萬左右的連接,2G內存的機器可以打開20萬左右的連接

2、FD劇增後帶來的IO效率問題

select

因爲每次調用時都會對連接進行線性遍歷,所以隨着FD的增加會造成遍歷速度慢的“線性下降性能問題”。

poll

同上

epoll

因爲epoll內核中實現是根據每個fd上的callback函數來實現的,只有活躍的socket纔會主動調用callback,所以在活躍socket較少的情況下,使用epoll沒有前面兩者的線性下降的性能問題,但是所有socket都很活躍的情況下,可能會有性能問題。

3、 消息傳遞方式

select

內核需要將消息傳遞到用戶空間,都需要內核拷貝動作

poll

同上

epoll

epoll通過內核和用戶空間共享一塊內存來實現的。

總結:

綜上,在選擇select,poll,epoll時要根據具體的使用場合以及這三種方式的自身特點。

1、表面上看epoll的性能最好,但是在連接數少並且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數回調。

2、select低效是因爲每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善 

 

關於這三種IO多路複用的用法,前面三篇總結寫的很清楚,並用服務器回射echo程序進行了測試。連接如下所示:

select:IO多路複用之select總結

poll:O多路複用之poll總結

epoll:IO多路複用之epoll總結

  今天對這三種IO多路複用進行對比,參考網上和書上面的資料,整理如下:

1、select實現

select的調用過程如下所示:

(1)使用copy_from_user從用戶空間拷貝fd_set到內核空間

(2)註冊回調函數__pollwait

(3)遍歷所有fd,調用其對應的poll方法(對於socket,這個poll方法是sock_poll,sock_poll根據情況會調用到tcp_poll,udp_poll或者datagram_poll)

(4)以tcp_poll爲例,其核心實現就是__pollwait,也就是上面註冊的回調函數。

(5)__pollwait的主要工作就是把current(當前進程)掛到設備的等待隊列中,不同的設備有不同的等待隊列,對於tcp_poll來說,其等待隊列是sk->sk_sleep(注意把進程掛到等待隊列中並不代表進程已經睡眠了)。在設備收到一條消息(網絡設備)或填寫完文件數據(磁盤設備)後,會喚醒設備等待隊列上睡眠的進程,這時current便被喚醒了。

(6)poll方法返回時會返回一個描述讀寫操作是否就緒的mask掩碼,根據這個mask掩碼給fd_set賦值。

(7)如果遍歷完所有的fd,還沒有返回一個可讀寫的mask掩碼,則會調用schedule_timeout是調用select的進程(也就是current)進入睡眠。當設備驅動發生自身資源可讀寫後,會喚醒其等待隊列上睡眠的進程。如果超過一定的超時時間(schedule_timeout指定),還是沒人喚醒,則調用select的進程會重新被喚醒獲得CPU,進而重新遍歷fd,判斷有沒有就緒的fd。

(8)把fd_set從內核空間拷貝到用戶空間。

總結:

select的幾大缺點:

(1)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大

(2)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大

(3)select支持的文件描述符數量太小了,默認是1024

2 poll實現

  poll的實現和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結構而不是select的fd_set結構,其他的都差不多,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是poll沒有最大文件描述符數量的限制。poll和select同樣存在一個缺點就是,包含大量文件描述符的數組被整體複製於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增加而線性增大。

3、epoll

  epoll既然是對select和poll的改進,就應該能避免上述的三個缺點。那epoll都是怎麼解決的呢?在此之前,我們先看一下epoll和select和poll的調用接口上的不同,select和poll都只提供了一個函數——select或者poll函數。而epoll提供了三個函數,epoll_create,epoll_ctl和epoll_wait,epoll_create是創建一個epoll句柄;epoll_ctl是註冊要監聽的事件類型;epoll_wait則是等待事件的產生。

  對於第一個缺點,epoll的解決方案在epoll_ctl函數中。每次註冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進內核,而不是在epoll_wait的時候重複拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。

  對於第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應的設備等待隊列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)併爲每個fd指定一個回調函數,當設備就緒,喚醒等待隊列上的等待者時,就會調用這個回調函數,而這個回調函數會把就緒的fd加入一個就緒鏈表)。epoll_wait的工作實際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select實現中的第7步是類似的)。

  對於第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統內存關係很大。

總結:

(1)select,poll實現需要自己不斷輪詢所有fd集合,直到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用epoll_wait不斷輪詢就緒鏈表,期間也可能多次睡眠和喚醒交替,但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,並喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,但是select和poll在“醒着”的時候要遍歷整個fd集合,而epoll在“醒着”的時候只要判斷一下就緒鏈表是否爲空就行了,這節省了大量的CPU時間。這就是回調機制帶來的性能提升。

(2)select,poll每次調用都要把fd集合從用戶態往內核態拷貝一次,並且要把current往設備等待隊列中掛一次,而epoll只要一次拷貝,而且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,注意這裏的等待隊列並不是設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省不少的開銷。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章