1. 程式人生 > >epoll為什麼快 及和select區別

epoll為什麼快 及和select區別

epoll是多路複用IO(I/O Multiplexing)中的一種方式,但是僅用於linux2.6以上核心,在開始討論這個問題之前,先來解釋一下為什麼需要多路複用IO.

以一個生活中的例子來解釋.

假設你在大學中讀書,要等待一個朋友來訪,而這個朋友只知道你在A號樓,但是不知道你具體住在哪裡,於是你們約好了在A號樓門口見面.

如果你使用的阻塞IO模型來處理這個問題,那麼你就只能一直守候在A號樓門口等待朋友的到來,在這段時間裡你不能做別的事情,不難知道,這種方式的效率是低下的.

現在時代變化了,開始使用多路複用IO模型來處理這個問題.你告訴你的朋友來了A號樓找樓管大媽,讓她告訴你該怎麼走.這裡的樓管大媽扮演的就是多路複用IO的角色.


進一步解釋select和epoll模型的差異.

select版大媽做的是如下的事情:比如同學甲的朋友來了,select版大媽比較笨,她帶著朋友挨個房間進行查詢誰是同學甲,你等的朋友來了,於是在實際的程式碼中,select版大媽做的是以下的事情:

int n = select(&readset,NULL,NULL,100);

for (int i = 0; n > 0; ++i)
{
   if (FD_ISSET(fdarray[i], &readset))
   {
      do_something(fdarray[i]);
      --n;
   }
}

epoll版大媽就比較先進了,她記下了同學甲的資訊,比如說他的房間號,那麼等同學甲的朋友到來時,只需要告訴該朋友同學甲在哪個房間即可,不用自己親自帶著人滿大樓的找人了.於是epoll版大媽做的事情可以用如下的程式碼表示:

n=epoll_wait(epfd,events,20,500);
for(i=0;i<n;++i)
{
    do_something(events[n]);
}

在epoll中,關鍵的資料結構epoll_event定義如下:
typedef union epoll_data {
                void *ptr;
                int fd;
                __uint32_t u32;
                __uint64_t u64;
        } epoll_data_t;

        struct epoll_event {
                __uint32_t events;      /*
 Epoll events */
                epoll_data_t data;      /* User data variable */
        };  可以看到,epoll_data是一個union結構體,它就是epoll版大媽用於儲存同學資訊的結構體,它可以儲存很多型別的資訊:fd,指標,等等.有了這個結構體,epoll大媽可以不用吹灰之力就可以定位到同學甲.

別小看了這些效率的提高,在一個大規模併發的伺服器中,輪詢IO是最耗時間的操作之一.再回到那個例子中,如果每到來一個朋友樓管大媽都要全樓的查詢同學,那麼處理的效率必然就低下了,過不久樓底就有不少的人了.

對比最早給出的阻塞IO的處理模型, 可以看到採用了多路複用IO之後, 程式可以自由的進行自己除了IO操作之外的工作, 只有到IO狀態發生變化的時候由多路複用IO進行通知, 然後再採取相應的操作, 而不用一直阻塞等待IO狀態發生變化了.

從上面的分析也可以看出,epoll比select的提高實際上是一個用空間換時間思想的具體應用.

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這一步的。