select epoll

關於select與epoll

兩種IO模型,都屬於多路IO就緒通知,提供了對大量文件描述符就緒檢查的高性能方案,只不過實現方式有所不同:

select:

一個select()系統調用來監視包含多個文件描述符的數組,當select返回,該數組中就緒的文件描述符便會被內核修改標誌位。

select的 跨平臺 做的很好,幾乎每個平臺都支持。

select缺點有以下三點:

  1. 單個進程能夠 監視的文件描述符的數量存在最大限制
  2. select()所維護的 存儲大量文件描述符的數據結構 ,隨着文件描述符數量的增長,其在用戶態和內核的地址空間的複製所引發的開銷也會線性增長
  3. 由於網絡響應時間的延遲使得大量TCP連接處於非活躍狀態,但調用select()還是會對 所有的socket進行一次線性掃描 ,會造成一定的開銷

poll:

poll是unix沿用select自己重新實現了一遍,唯一解決的問題是poll 沒有最大文件描述符數量的限制

epoll:

epoll帶來了兩個優勢,大幅度提升了性能:

  1. 基於事件的就緒通知方式 ,select/poll方式,進程只有在調用一定的方法後,內核纔會對所有監視的文件描述符進行掃描,而epoll事件通過epoll_ctl()註冊一個文件描述符,一旦某個文件描述符就緒時,內核會採用類似call back的回調機制,迅速激活這個文件描述符,epoll_wait()便會得到通知
  2. 調用一次epoll_wait()獲得就緒文件描述符時,返回的並不是實際的描述符,而是一個代表就緒描述符數量的值,拿到這些值去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這裏使用內存映射(mmap)技術, 避免了複製大量文件描述符帶來的開銷

當然epoll也有一定的侷限性, epoll只有Linux2.6纔有實現 ,而其他平臺都沒有,這和apache這種優秀的跨平臺服務器,顯然是有些背道而馳了。

select的本質是採用32個整數的32位,即32*32= 1024來標識,fd值爲1-1024。當fd的值超過1024限制時,就必須修改FD_SETSIZE的大小。這個時候就可以標識32*max值範圍的fd。

對於單進程多線程,每個線程處理多個fd的情況,select是不適合的。

1.所有的線程均是從1-32*max進行掃描,每個線程處理的均是一段fd值,這樣做有點浪費

2.1024上限問題,一個處理多個用戶的進程,fd值遠遠大於1024

所以這個時候應該採用poll,

poll傳遞的是數組頭指針和該數組的長度,只要數組的長度不是很長,性能還是很不錯的,因爲poll一次在內核中申請4K(一個頁的大小來存放fd),儘量控制在4K以內

epoll還是poll的一種優化,返回後不需要對所有的fd進行遍歷,在內核中維持了fd的列表。select和poll是將這個內核列表維持在用戶態,然後傳遞到內核中。但是只有在2.6的內核才支持。

epoll更適合於處理大量的fd ,且活躍fd不是很多的情況,畢竟fd較多還是一個串行的操作

=====================================================================

對select、poll、epoll瞭解得不多,下面是從《構建高性能Web站點》摘錄下來的介紹,等以後真正接觸到select、poll和epoll方面的開發再詳細寫一下使用上的區別。

select

select最早於1983年出現在4.2BSD中,它通過一個select()系統調用來監視多個文件描述符的數組,當select()返回後,該數組中就緒的文件描述符便會被內核修改標誌位,使得進程可以獲得這些文件描述符從而進行後續的讀寫操作。

select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優點,事實上從現在看來,這也是它所剩不多的優點之一。

select的一個缺點在於單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般爲1024,不過可以通過修改宏定義甚至重新編譯內核的方式提升這一限制。

另外,select()所維護的存儲大量文件描述符的數據結構,隨着文件描述符數量的增大,其複製的開銷也線性增長。同時,由於網絡響應時間的延遲使得大量TCP連接處於非活躍狀態,但調用select()會對所有socket進行一次線性掃描,所以這也浪費了一定的開銷。

poll

poll在1986年誕生於System V Release 3,它和select在本質上沒有多大差別,但是poll沒有最大文件描述符數量的限制。

poll和select同樣存在一個缺點就是,包含大量文件描述符的數組被整體複製於用戶態和內核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨着文件描述符數量的增加而線性增大。

另外,select()和poll()將就緒的文件描述符告訴進程後,如果進程沒有對其進行IO操作,那麼下次調用select()和poll()的時候將再次報告這些文件描述符,所以它們一般不會丟失就緒的消息,這種方式稱爲水平觸發(Level Triggered)。

epoll

直到Linux2.6纔出現了由內核直接支持的實現方法,那就是epoll,它幾乎具備了之前所說的一切優點,被公認爲Linux2.6下性能最好的多路I/O就緒通知方法。

epoll可以同時支持水平觸發和邊緣觸發(Edge Triggered,只告訴進程哪些文件描述符剛剛變爲就緒狀態,它只說一遍,如果我們沒有採取行動,那麼它將不會再次告知,這種方式稱爲邊緣觸發),理論上邊緣觸發的性能要更高一些,但是代碼實現相當複雜。

epoll同樣只告知那些就緒的文件描述符,而且當我們調用epoll_wait()獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可,這裏也使用了內存映射(mmap)技術,這樣便徹底省掉了這些文件描述符在系統調用時複製的開銷。

另一個本質的改進在於epoll採用基於事件的就緒通知方式。在select/poll中,進程只有在調用一定的方法後,內核纔對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來註冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會採用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。

 

epoll的優點:

1.支持一個進程打開大數目的socket描述符(FD)
select 最不能忍受的是一個進程所打開的FD是有一定限制的,由FD_SETSIZE設置,默認值是2048。對於那些需要支持的上萬連接數目的IM服務器來說顯然太少了。這時候你一是可以選擇修改這個宏然後重新編譯內核,不過資料也同時指出這樣會帶來網絡效率的下降,二是可以選擇多進程的解決方案(傳統的 Apache方案),不過雖然linux上面創建進程的代價比較小,但仍舊是不可忽視的,加上進程間數據同步遠比不上線程間同步的高效,所以也不是一種完美的方案。不過 epoll則沒有這個限制,它所支持的FD上限是最大可以打開文件的數目,這個數字一般遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統內存關係很大。

2.IO效率不隨FD數目增加而線性下降
傳統的select/poll另一個致命弱點就是當你擁有一個很大的socket集合,不過由於網絡延時,任一時間只有部分的socket是"活躍"的,但是select/poll每次調用都會線性掃描全部的集合,導致效率呈現線性下降。但是epoll不存在這個問題,它只會對"活躍"的socket進行操作---這是因爲在內核實現中epoll是根據每個fd上面的callback函數實現的。那麼,只有"活躍"的socket纔會主動的去調用 callback函數,其他idle狀態socket則不會,在這點上,epoll實現了一個"僞"AIO,因爲這時候推動力在os內核。在一些 benchmark中,如果所有的socket基本上都是活躍的---比如一個高速LAN環境,epoll並不比select/poll有什麼效率,相反,如果過多使用epoll_ctl,效率相比還有稍微的下降。但是一旦使用idle connections模擬WAN環境,epoll的效率就遠在select/poll之上了。

3.使用mmap加速內核與用戶空間的消息傳遞。
這點實際上涉及到epoll的具體實現了。無論是select,poll還是epoll都需要內核把FD消息通知給用戶空間,如何避免不必要的內存拷貝就很重要,在這點上,epoll是通過內核於用戶空間mmap同一塊內存實現的。而如果你想我一樣從2.5內核就關注epoll的話,一定不會忘記手工 mmap這一步的。

4.內核微調
這一點其實不算epoll的優點了,而是整個linux平臺的優點。也許你可以懷疑linux平臺,但是你無法迴避linux平臺賦予你微調內核的能力。比如,內核TCP/IP協議棧使用內存池管理sk_buff結構,那麼可以在運行時期動態調整這個內存pool(skb_head_pool)的大小--- 通過echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函數的第2個參數(TCP完成3次握手的數據包隊列長度),也可以根據你平臺內存大小動態調整。更甚至在一個數據包面數目巨大但同時每個數據包本身大小卻很小的特殊系統上嘗試最新的NAPI網卡驅動架構。

 

Apache與Nginx:

Apache與Nginx的性能誰更高效,取決於其服務器的併發策略以及其面對的場景:

併發策略:

我們目前使用的 Apache是基於一個線程處理一個請求的非阻塞IO併發策略 。這種方式允許一個進程中通過多個線程來處理多個連接,其中每個線程處理一個連接。Apache使用其worker模塊實現這種方式,目的是減少perfork模式中太多進程的開銷,使得apache可以支持更多的併發連接。

至於,非阻塞IO的實現,是通過一個子進程負責accept(),一旦接收到連接後,便將任務分配給適當worker的線程。

由於apache的線程使用的是內核進程調度器管理的輕量級進程,因此與perfork模式比較,進程上下文切換的開銷依然存在,性能提升不是很明顯。

Nginx使用的是一個進程處理多個連接、非阻塞IO模式 ,這種模式最特別的是設計了獨立的listener進程,專門負責接收新的連接,再分配給各個worker,當然爲了減少任務調度的開銷,一般都是由worker進程來進行接收。

而IO模型層面,Nginx選擇epoll,此方式高效主要在於其基於事件的就緒通知機制,在高連接數的場景下,epoll通知方式更具優勢。另外,epoll方式只關注活躍連接,而不像select方式需要掃描所有的文件描述符,這樣在大量連接的場景下,epoll方式優勢會更加明顯。

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