epoll和其他技术的性能比较.
! N+ C! I6 W9 }$ j& ^# C% _4 Q' k: \: n' w& ]
翻译:韩红军。hanhj@vrlab.buaa.edu.cn ;
hongjun_han@163.com
5 L; |: m/ n# q9 W" |% w, G原文出自:
https://www.captech.com.cn( z) m/ L5 z0 s2 P! T+ F1 F
英文原文:
http://www.xmailserver.org/linux-patches/nio-improve.html5 L7 D' K/ R1 J$ N
由于水平有限,错误在所难免,希望各位指正。
1 m/ ` T4 g& q! y: \3 q07-01-2001 ? 初稿 - Davide Libenzi <davidel@xmailserver.org>
2 J$ X" C# {: X {8 {. }
10-30-2002 ? epoll补丁成为Linux内核一部分。请参考这个版本的,因为这个版本将会成为标准,并得到广泛支持Davide Libenzi <davidel@xmailserver.org>
$ f* V" S8 {0 p( h2 t H- B绪论:
8 I8 g9 I1 }% Q0 k) G8 P# @
眼下的工作是分析不同的方法,这些方法都是用来实现从内核模式高效传递网络事件到用户模式。我们考察了五种方法:作为一种相对较好的老的方法poll ,标准/dev/poll接口,标准RT信号,RT signals with one-sig-per-fd patch,和使用很特别的通知方法的新式/dev/epoll。工作有如下四部分组成:
* I0 r, \6 ^) S. u7 L/ ]% s6 B2 {
1) 新式 /dev/epoll 内核补丁
0 c- @1 X! k2 o3 K% ~% F, S
2) Provos-Lever修改的能够在内核 2.4.6 工作的/dev/poll 补丁
6 x) c0 e2 x* Q& l$ |: O: u% y4 R N3) HTTP server
: v5 F' J2 _& a6 a0 g4) 一个能够产生“dead“连接的deadconn(tm)工具。
3 u0 y' ^: X' M7 B( a
Httperf被采用作为度量工具,尽管不完美,但也提供了足够的网络负载选项。
9 N5 G; B; s% O; r+ y1 R8 [新式 /dev/epoll 内核补丁:
7 T( q' {0 {% m V2 B/ @( F
这个补丁很简单,它在struct file的数据结构里面添加了通知回调链表项。如下代码段:
+ k5 K# ?9 ^; k0 Z代码:
* ?& z9 C$ ?4 x1 a# p" M
******* include/linux/fs.h
) m' Q) R6 {; P" M% ]struct file {
' h$ q2 s! @' w6 S" z8 C...
. n+ n; Z N7 `% } B
/* 文件回调列表 */
- o7 B; b! ], vrwlock_t f_cblock;
$ I# m4 y; X9 [, A9 n& B5 J/ U+ I6 Hstruct list_head f_cblist;
* `( a+ w8 ~1 j% l1 v. ~. V V1 ]5 v- i};
5 k) m9 K* C, A% d- c0 q( o2 o p" k3 C/ r* M7 F
3 q$ m& r! G# a( Z+ c1 E4 \) [! `! p4 |% |0 J @7 M
代码:
5 ^/ t( o6 ~3 A$ K
/ R7 Z: ^/ [0 e$ Q. u
****include/linux/fcblist.h
* H1 p( f) H s* @& y$ q9 h/* 文件回调通知事件 */
, _: Z# k0 W- ?8 J2 U" v1 K$ t1 M
#define ION_IN 1
1 Y, y8 w& m* E9 L& [
#define ION_OUT 2
3 P# p3 Q. m2 t#define ION_HUP 3
7 z S. V# A. P1 e) U#define ION_ERR 4
! [% M7 I2 y2 i3 J6 `9 K7 c& V/ D# z% H& f
#define FCB_LOCAL_SIZE 4
7 u; S7 q( \2 e* T M+ D7 r' w/ e7 r! x: n& u) h$ N( w
#define fcblist_read_lock(fp, fl) read_lock_irqsave(&(fp)->f_cblock, fl)
* V6 r# R) F# c) ^
#define fcblist_read_unlock(fp, fl) read_unlock_irqrestore(&(fp)->f_cblock, fl)
$ G/ E; p) Q' l3 `/ m#define fcblist_write_lock(fp, fl) write_lock_irqsave(&(fp)->f_cblock, fl)
& r9 t8 N% W$ F) k& R2 _7 x
#define fcblist_write_unlock(fp,fl) write_unlock_irqrestore(&(fp)->f_cblock, fl)
; v. B/ |. ~( b5 L
; P3 K: M4 \* S9 U* M1 x: H' ?struct fcb_struct {
1 D) e, _3 e# _. cstruct list_head lnk;
+ W+ O$ {/ I' K G0 P! T
void (*cbproc)(struct file *, void *, unsigned long *, long *);
' E: G( s/ {, N0 x9 _% {3 h0 U* Fvoid *data;
* E. S8 j% [ s% o/ x! o; q- _unsigned long local[FCB_LOCAL_SIZE];
. S& e) w+ A# C& y# C. z};
/ b, L# y6 V/ O* s
* x; ^7 @* q) q. C& [( @, k. {1 O7 Z! W$ c" O2 F& z+ @. N/ u7 Q
extern long ion_band_table[];
. F+ J7 r3 f# }5 R
extern long poll_band_table[];
% F6 K$ i3 T, h4 B$ r* D0 {3 [7 i; U* t L4 ], o! y
: t V* Y! v9 q; c* Q8 f+ E' R$ E: K% W
static inline void file_notify_init(struct file *filep)
) m. J+ I4 Z6 l6 I r: r* F: k3 x H{
0 Q5 t. V; }5 o: d5 H8 Irwlock_init(&filep->f_cblock);
/ K; \. h6 W6 a$ d! F
INIT_LIST_HEAD(&filep->f_cblist);
/ U2 w$ H3 d" s- \$ @2 Q4 P
}
+ ?" [/ f% ]" n, U7 w
" [* b; W6 r- W0 s/ E% Vvoid file_notify_event(struct file *filep, long *event);
: ?! h' }; G+ ^$ n2 g
' x2 v/ Y. I3 n# o2 @* Aint file_notify_addcb(struct file *filep,
/ v' r5 H. I6 W( S; {; c& }2 G
void (*cbproc)(struct file *, void *, unsigned long *, long *), void *data);
7 {" s% a" L. w) n* s
2 R% ?' d; \# W7 hint file_notify_delcb(struct file *filep,
# | m7 Z4 y) s% Z* [# T9 ?void (*cbproc)(struct file *, void *, unsigned long *, long *));
: H1 ^+ x$ t. Y: f5 m& ]. D" D, D6 r6 `- P) k& A6 x+ S
void file_notify_cleanup(struct file *filep);
7 ^* `: T F4 i8 q; N
4 F8 t0 f$ c1 a- U, }4 ^1 M这些回调方法链就是提供一种机制,向文件系统注册其“兴趣”的上层在兴趣事件发生时能够收到底层I/O的通知。初始化和清理代码已经加在了fs/file_table.c里,回调方法链处理代码在fs/fcblist.c里面,如下所示:
& K+ d# I- N# D
代码:
2 n, b" |* | U5 z$ L' D M8 {7 l
****** fs/file_table.c
; C; Q5 p' u3 I% R8 s% C: r
struct file * get_empty_filp(void)
0 C4 }) G* q; E% \
{
5 ^% \$ K( E8 g$ p @3 a...
; R2 }7 b' C9 U: ^$ W* h; M3 N& U
file_notify_init(f);
9 O5 I3 G! \2 O' }0 f: V...
+ F0 {' g! I% C9 x, S; T( p/ r* ?
}
( v) S$ k) @9 v. e) t0 r0 b8 t
% }4 J3 |% A2 {int init_private_file(struct file *filp, struct dentry *dentry, int mode)
) u: m! x$ ^3 [# d5 w z{
; e8 `4 O3 }6 C...
+ Q4 ~% y% W3 G. I# G& ]
file_notify_init(filp);
$ @# I8 i" l! f" O7 w+ B
...
; U% a9 M4 ]# D ], m# ^/ V
}
& Q' ~0 A! B. C- h% B5 o
9 t! @; b# }% m6 p0 _* U8 g
void fput(struct file * file)
: {2 a8 Q; r9 `+ L- S0 f{
. h4 y/ @$ N' B$ r% u...
2 N$ o- P3 @" K$ }3 p
file_notify_cleanup(file);
, R" q/ w" I. Z
...
3 \& d4 \1 G1 b4 a8 x. ]5 F* a1 L
}
# h2 w) | U$ h: x( d+ |( T9 @; U****** fs/fcblist.c
9 C; Y D5 V: E4 |5 S: Y' avoid file_notify_event(struct file *filep, long *event)
5 E7 _; @+ C0 j, ^; \- p{
^2 P, v5 `) t) w( ]! i8 ^2 kunsigned long flags;
2 X# \4 Z+ P& ~0 _" R. ^( O+ m
struct list_head *lnk;
3 z( j, ?9 S* }
( R! z0 i$ i( {5 v" k+ Vfcblist_read_lock(filep, flags);
6 ~7 A4 l3 R: B! ?3 olist_for_each(lnk, &filep->f_cblist) {
" ^2 _& ]. i8 a8 a A% T7 Istruct fcb_struct *fcbp = list_entry(lnk, struct fcb_struct, lnk);
4 R' `. a% d0 E3 m" f7 o0 ?$ `' B- F z+ A/ r0 `; e. J/ |9 q
fcbp->cbproc(filep, fcbp->data, fcbp->local, event);
! {: L. J* d, J) m* C8 {; `' g
}
9 g/ l" z1 Q( `, _- k0 l3 G$ H& k+ b
fcblist_read_unlock(filep, flags);
( ~2 O9 N+ R/ ~1 z, r}
, r) |: j: q' e
. _! L/ t) q3 A4 ~ ]
int file_notify_addcb(struct file *filep,
; g0 C0 d2 \8 e7 |4 f7 |. x7 d/ Y( \void (*cbproc)(struct file *, void *, unsigned long *, long *), void *data)
0 `# l( G1 Y2 o{
0 c9 w3 ]; e) kunsigned long flags;
/ p1 W9 o7 ^# Q+ P
struct fcb_struct *fcbp;
f. R% z* z1 W, e
, u' ]4 c4 i4 }; J5 Hif (!(fcbp = (struct fcb_struct *) kmalloc(sizeof(struct fcb_struct), GFP_KERNEL)))
( Z8 y3 O7 G: a# X r# y+ zreturn -ENOMEM;
" v+ _# e# V/ }% q; Y9 C# _7 imemset(fcbp, 0, sizeof(struct fcb_struct));
4 E* s* _0 D9 C- ^% ^; Ufcbp->cbproc = cbproc;
( j' L( l5 C+ z R4 j/ y% b
fcbp->data = data;
3 _4 I; C$ D5 [7 b9 `. n
fcblist_write_lock(filep, flags);
" _- z' t) t: ]6 q& b, v
list_add_tail(&fcbp->lnk, &filep->f_cblist);
! E$ x1 A% x$ J" M2 G+ c2 V
fcblist_write_unlock(filep, flags);
3 Z& N0 ~& v9 `- H k l1 L3 s
return 0;
0 N' b$ y6 _- Y0 ?0 Z4 Q2 S# y}
; W4 F* l5 A+ h. ^, h8 f
, [% f2 y3 y9 t. v- Tint file_notify_delcb(struct file *filep,
4 [, P4 ^ n% {( Ivoid (*cbproc)(struct file *, void *, unsigned long *, long *))
w- @/ {0 ^9 u) e9 B k( r{
( t1 J4 ^2 h6 H5 \# I# j4 J8 Uunsigned long flags;
4 E, E8 x( G# Q. c8 T, Kstruct list_head *lnk;
# T1 K. J, Y4 e: p# G' `5 h
) V- y, s6 `2 C2 f: v4 Gfcblist_write_lock(filep, flags);
/ S0 V% {. K0 Ulist_for_each(lnk, &filep->f_cblist) {
6 d, t/ }9 E: ?1 pstruct fcb_struct *fcbp = list_entry(lnk, struct fcb_struct, lnk);
; E# b; g+ f+ T9 E
3 G+ _; ?; ]2 r/ k! fif (fcbp->cbproc == cbproc) {
; S( k4 p5 ]6 L) Y! ]
list_del(lnk);
4 X; Y' {: p% J: y
fcblist_write_unlock(filep, flags);
0 p$ c$ g$ I# r1 q
kfree(fcbp);
' r7 \, W% T! D( }$ Nreturn 0;
/ ~+ D! x2 Q0 C# ?}
) F, s8 j! e3 B* B9 P
}
7 z' ~: W S: `! C0 n
fcblist_write_unlock(filep, flags);
9 W6 J! F0 ~1 p: M/ F2 r4 Rreturn -ENOENT;
, Z' }- Q5 D% k# n7 A, _4 U
}
% B- E; F+ e! ]! M( R* M" U& t
; o2 b1 z( d$ P7 \7 ^void file_notify_cleanup(struct file *filep)
5 Y. I& K } e$ o{
d7 |/ u# S' M5 Y& q t/ }( runsigned long flags;
@; i/ b& ]. p5 Q; N3 h( \7 \struct list_head *lnk;
2 f/ i2 p) K! \( ~6 U$ T& |# C& ~/ ^+ c2 C- X4 a( Z+ U+ e5 _; q1 @( ?
fcblist_write_lock(filep, flags);
+ G( Q u; C$ f( Bwhile ((lnk = list_first(&filep->f_cblist))) {
/ T" W+ o& [3 @
struct fcb_struct *fcbp = list_entry(lnk, struct fcb_struct, lnk);
. [+ `# X& ^4 \" B
]- d' W) \& G! p5 ]8 ~9 x' ?list_del(lnk);
* x9 d# \7 Y0 y" w: L( R6 A
fcblist_write_unlock(filep, flags);
' s# E8 K. [0 D# c* r
kfree(fcbp);
' k) L, C- y5 |# }* efcblist_write_lock(filep, flags);
: s4 r* b2 m! p; @- ?4 m
}
) s0 F; }7 j7 j8 h$ _0 wfcblist_write_unlock(filep, flags);
0 f2 N" M6 p8 r: n- z# P
}
5 ?7 _" `/ H' o6 u. f, M
2 N0 a* C( L% t" L: \: P$ b
这些回调函数会收到一个long *参数,其第一个元素是ION_*事件中的一种,下一个可以存储额外的参数,这些额外参数的意义随这第一个元素即事件种类的不同而不同。
6 p) G1 K' i# k5 m [9 U( o: e1 ^这个接口只是一个草案,我使用它只是验证传输方法是不是足够高效。目前通知只在socket文件出现,如下:
& O0 e. e3 k, b) y
代码:
' D' _) o/ ~9 c5 `2 p* \, G8 U
****** include/net/sock.h
, s$ ^0 H) B# R( z/ m# c6 b, E+ d
static inline void sk_wake_async(struct sock *sk, int how, int band)
! \% o; S. x U6 y
{
1 H4 T9 ]4 O+ [; O: b9 p1 b' k tif (sk->socket) {
5 V0 S) u* _- h& S7 A0 [4 nif (sk->socket->file) {
3 q* y& a8 W8 I; D( v
long event[] = { ion_band_table[band - POLL_IN], poll_band_table[band - POLL_IN], -1 };
! u }* b: x. m! m- K4 W( A6 {* F, d# p
file_notify_event(sk->socket->file, event);
6 Y' A9 s. e5 |( ^; v
}
; Q& d& u: F" S4 x4 g
if (sk->socket->fasync_list)
8 k! |. U( C; B ~! M( v- F1 ~) u" C
sock_wake_async(sk->socket, how, band);
& j2 L( S& _( ], d& y, W, K
}
5 F" G% e- K8 Z9 ^}
' e) H. \0 p W# i7 H* [, p+ i
/ b0 \, X) E( F3 t文件fs/pipe.c和include/linux/pipe_fs_i.h也都被修改以扩展/dev/epoll的功能至管道(pipes pipe() )
1 W( N) S9 m- k7 {* X& ~8 b- ^" ~
两个新的文件driver/char/eventpoll.c和include/linux/eventpoll.h实现了/dev/epoll。
u+ A+ g+ V7 t( J6 g/ D/ Z
新的/dev/epoll接口和以前的有很大的不同,因为他只是通过设备文件描述符映射,而基于效率考虑放弃了拷贝数据到用户空间(copy-data- to-user-space),通过共享内存页来避免不必要的拷贝数据/dev/epoll可以高效运作,理由有1)更少的CPU周期,因为不必拷贝数据,2)在现代的缓存的存储体系架构下,有更少的内存占用(memory footprint)。
3 X! W9 f6 D% ^ m/dev/epoll实现利用新的文件回调通知机制来注册回调方法,这些回调方法将事件存储事件缓冲区里,初始化顺序如下:
1 U$ l/ t5 [, G1 w+ B8 _8 C代码:
3 G6 Z. E/ `, ~- K! y9 j7 u2 D& K/ Sif ((kdpfd = open("/dev/epoll", O_RDWR)) == -1) {
' a5 ~$ X4 L5 R" B, M0 [. z2 R% ^2 F% I* D7 N
}
7 W1 E) v( l1 h3 `if (ioctl(kdpfd, EP_ALLOC, maxfds))
' I8 [# H7 I' Q9 S1 `
{
4 u" B/ g; r0 P: s7 V
A I1 b: I4 T}
( _2 T; H f; v7 h. [3 y: Y
if ((map = (char *) mmap(NULL, EP_MAP_SIZE(maxfds), PROT_READ,
: P4 `7 d# r) U: QMAP_PRIVATE, kdpfd, 0)) == (char *) -1)
* \2 u/ ]8 x) M! j+ l( Y; a( v
{
1 w g9 C6 Q# D2 j1 S$ {% U* |
" F5 m3 y4 }2 P! [5 }7 i
}
" ~1 a- h. x! Y
$ ~/ F" q" e3 h% U& K, X
maxfds 能够存储在轮询设备里面的最多的文件描述符数目,文件通过下面代码注册进兴趣集里面:
& L2 q! p X7 I# N `
代码:
' R3 m5 W2 A9 R" u5 O) [
struct pollfd pfd;
9 M- _- ^$ ^" t# k) f
pfd.fd = fd;
: E4 g9 p4 w$ ?& W
pfd.events = POLLIN | POLLOUT | POLLERR | POLLHUP;
- K4 t- b n: n* i# i% mpfd.revents = 0;
6 }( Y* {8 _$ r3 Xif (write(kdpfd, &pfd, sizeof(pfd)) != sizeof(pfd)) {
+ L$ `1 n0 U8 p* ~+ t$ h* m6 L...
k# v% z, J+ p: L4 S- e5 n! P6 S
}
! B/ R& w3 {8 S4 b! j- ~* Y0 l& w; x: c' t& b) z8 ?0 u3 e) [
下面代码删除指定的文件描述符:
$ ]7 H( E$ a Q5 t
代码:
9 v" n4 [* o2 r9 @/ _; X
struct pollfd pfd;
! M1 p$ @ n) u. B( O
pfd.fd = fd;
7 _3 d* P0 s& k% R( Xpfd.events = POLLREMOVE;
4 a3 p$ R( R/ K) [' E6 N( i, V; N W: p- m
pfd.revents = 0;
6 ^' y$ Z6 p8 z: k
if (write(kdpfd, &pfd, sizeof(pfd)) != sizeof(pfd)) {
, K; ?2 \0 e5 H4 i4 s
...
3 n$ N: J# j8 {8 x9 h
}
! |& e7 w) ]2 Y) `. `
! s2 ?- H: ^! D: J$ }核心分派(dispatch)代码类似下面:
2 p2 T* d! |' ?! [5 M( r代码:
) H$ \) G1 V/ n' Rstruct pollfd *pfds;
) G( R0 W- B# q; I7 dstruct evpoll evp;
0 c8 N5 \8 K! G2 t* N7 L) l! _
/ g- K, c2 B; E8 q7 i' \3 [/ {" C
for (;;) {
/ M2 ]. ]* n. Q |* @$ Gevp.ep_timeout = STD_SCHED_TIMEOUT;
9 d2 }7 [3 M# k
evp.ep_resoff = 0;
; Y! J* j `7 r6 f1 t, b
* p1 t1 [" {+ O9 T P8 L7 Wnfds = ioctl(kdpfd, EP_POLL, &evp);
6 f! L6 V8 B$ }5 Y
pfds = (struct pollfd *) (map + evp.ep_resoff);
% G2 c" Q: G( C" N
for (ii = 0; ii < nfds; ii++, pfds++) {
% H. t: D) e4 u/ I% S
...
% s+ _! P; a H9 _% n, B0 ~+ [
}
* F5 c* K3 P! X4 \& X}
- r' e- m7 ~" Y
' W+ H) `! d+ t* o8 D2 f! O0 \& c, |: Z
驱动程序分配两个页面集来作为双缓冲机制,存储文件事件。可以从ep_resoff字段得知结果集在map里面的哪一块。在使用其中一个页面集的同时,内核可以使用另外一个存储进来的事件们。没有拷贝到用户空间的麻烦,来自同一个文件的事件将被放在一个槽(slot)里面,EP_POLL函数决不会线性查找兴趣集来执行file->f_ops->poll()。为了使用/dev/epoll接口,你必须使用mknod创建该文件,并且 major =10 and minor=124。命令如下:
+ z+ M- z. r/ s1 [" f" G& Z! x# mknod /dev/epoll c 10 124
( L, N, R% {8 o* t& y
下载补丁在下面的链接
* t. I: o1 j3 s5 repoll-lt-2.4.24-0.20.diff
" U! G* w1 u6 r+ K( {
. E( f" M: G4 W% V# V0 w: ^ C$ }Provos-Lever的/dev/poll补丁:
8 R3 r2 ]9 t5 E: @1 j- S3 Q; O
这个值得说的不多,修改virt_to_page()这个漏洞就可以使补丁正常工作,我修改了另外一个bug,程序为了调节哈希表大小,而试图使用 kmalloc()分配大块内存却不能满足,现在使用vmalloc()来为哈希表分配内存。我修改了在CITI网站上发现的2.4.3的补丁,这个补丁应该是Provos-Lever用来使(2.2.x)升级到2.4.x的那个。你可以在如下地址下载到:
5 c3 b' a1 X- i+ C
http://www.xmailserver.org/linux-patches/olddp_last.diff.gz
; D# N3 u! `( c! n, W* T$ h; l2 F6 Y& c
, P6 a, s9 o K# ]9 p) X, i实时信号一个文件描述符一个信号(one-sig-per-fd)补丁:
: h- v9 }$ i; [0 J: qVitaly Luban贡献的这个补丁实现了实时信号量,当实时信号量队列满了的时候,尽量避免SIGIO传输信息,你可以下载这个补丁如下网址:
4 _6 M6 ^ V- s# ?, }( {http://www.luban.org/GPL/gpl.html* N' v5 }3 j2 k i% W$ p y! P0 b
, ?/ X3 E, s. O' h4 WHTTP服务器:
& z( @, d {0 w( S- u8 t
http服务器很简单,它使用的是事件轮询和同步程序库(coroutines),服务器内部的coroutine库的实现来自如下网址:
9 I5 d& e. M @0 ?- z. ohttp://www.goron.de/~froese/coro/( i0 R) }2 J; G, e5 X4 g; {
它很小,简单且高效。服务器使用的默认堆栈大小为8192,当试图有许多连接时,将造成内存浪费和虚存颠簸(vm trashing),其实4096的堆栈大小就足够了。另外一个问题是coro库使用mmap()方法来为堆栈分配空间,这样当accept() /close()比率较高时会损失性能,我改了这个库(只有一个文件coro.c)让其使用malloc()/free()而不是mmap() /munmap().每次的Http应答都一样,应答的报文大小可以通过命令行参数设置,另外还有两个命令行参数可以设置监听端口和fd集的大小。你可以在如下地址下载到服务器:
' e+ @0 m5 w) { z0 U. `' y" vephttpd-0.2.tar.gz
: O1 f" H* ]% W8 A老版本:
9 Z+ T, W- E) r) v* ?' q- e7 F
http://www.xmailserver.org/linux-patches/dphttpd_last.tar.gz+ {# m9 h* `; A6 m( w5 t/ g
7 W" y8 X1 B* o! ]
死连接工具:
+ U- E4 V1 _( r w2 u. z+ ~' S
如果说上面的服务器简单,这个就更简单了,它的目的就是给服务器制造死连接来模拟真实的负栽,这些连接都是一批的低速连接。你可以下载到:
( `& _9 F! M( C8 o5 g& `http://www.xmailserver.org/linux-patches/deadconn_last.c
3 G _2 X# V! q/ }4 d+ F# r6 T$ y) s- z6 B' ?9 B
测试:
/ `: V. R/ Q. F, U' ]- X
测试环境:PIII 600MHz, 128 Mb RAM, eepro100网卡连接到100Mbps快速以太网交换机上。RH 6.2,使用的内核是2.4.6, coroutine 库的版本是1.1.0-pre2
4 T- y% z( @1 ]- D我使用双PIII 1GHz, 256 Mb RAM 和双eepro100作为httperf机器,双PIII 900 MHz, 256 Mb RAM 和双 eepro100作为deadconn(tm)机器,因为httperf当使用较高的num-conns时占用fds空间(改为8000了)很快消失,我采用了下面的命令行:
) p" x8 c/ ~- R a4 T--think-timeout 5 --timeout 5 --num-calls 2500 --num-conns 100 --hog --rate 100
6 t8 p" b, x4 \
这个将分配100个连接,使服务器负栽不同的死连接。我改变的另一个参数是应答报文大小有128,512和1024。另外一个更加忠实于互联网会话就是使用一些突发连接发出HTTP请求,然后关闭,测试使用httperf,并有如下命令:
$ b' f& G& O3 t, X
--think-timeout 5 --timeout 5 --num-calls 2 --num-conns 27000 --hog --rate 5000
& P" w* n* r* Q8 R; T. \1 d
每个数字都是求得三次的平均值,如下地址下载httperf:
5 v* ]( T2 a0 I/ }% C0 qhttp://www.hpl.hp.com/personal/David_Mosberger/httperf.html" t+ x# d+ W- g8 b/ s( p
1 B8 O" ]! s; R: E$ U4 f* O' R( B7 A
: g" ^1 q, S- C% G
; c1 e7 {/ G/ Y- w
测试显示/dev/epoll约比RT signals one-sig实现快10-12%,/dev/epoll和RT signals都保持平坦在不同的死连接负栽下。
' b+ ]% x) o! X- \/ v1 ^7 X( x3 |
RT-one-sig实现比简单 实时信号实现要快一点, 但是本测试只有很少的 SIGIO.
3 f* [4 J2 p; w6 s" `0 e/ E7 H
( r/ z3 s' K; X
3 y0 I; E5 W3 G6 `8 W2 O) @* |( T+ A) V. Z; s7 ^
4 b* m5 o9 W; N3 J& ^
512和1024 Content-Length 的应答报文测试表明 /dev/epoll, RT signals和RT one-sig 表现基本一致,图中重叠在一块了, 这是因为在本测试中以太网达到了饱和态( 100Mbps )。
7 g; d4 G$ Z. W- Q: L9 F: O* s
# s/ [- O! Z; t; D M( ?& |. V9 m% _0 X
这个测试表明/dev/epoll, RT signals 和RT one-sig实现在不同死连接情况下的每秒的Http应答报文一直平坦,并且/dev/epoll大约15% 快于RT one-sig ,而RT one-sig 大约10-15% 快于简单 RT signals.
9 O" l8 q& M- q0 }' H' d) Y/ O: j
" k8 W2 J+ G. q* X
系统调用接口( aka sys_epoll ):
2 U4 @& o' U8 u. H* i事件查询设备对于系统调用接口的需要促成了sys_epoll系统调用的实现,这个简单接口为开发人员实现了同样的可扩展性。新的系统调用引入了三个新的映射到用户空间的调用:
6 N7 a) u9 H: Nint epoll_create(int maxfds);
- ^1 s7 t$ w9 v) y+ T: i) qint epoll_ctl(int epfd, int op, int fd, unsigned int events);
0 K9 |! F2 V8 X- f
int epoll_wait(int epfd, struct pollfd *events, int maxevents, int timeout);
/ X5 @. E0 N* }# u# k这些函数在他们的手册页面里面描述:
+ z; Q1 H/ D) y- f
epoll : PSTXTMAN
. l- e' m/ J3 x; a3 A; R' t! b6 h' y- ?epoll_create : PS TXTMAN
) P/ W, j, I( D9 k. Xepoll_ctl : PSTXT MAN
. @+ i9 p$ x4 m$ Z5 G
epoll_wait : PSTXT MAN
& s% c6 Q7 v; i8 z: s$ G& f/ D
实现这些系统调用的补丁在这儿. 一个新的访问epoll ( 2.5.45 )的库在这个可得到: epoll-lib-0.11.tar.gz
5 D; H0 l: l, v! b: `
6 X5 ]' y# E6 p: v& P; Q9 O7 {
一个简单基于管道的epoll性能测试:
- b) }7 ^1 T8 ]. y) l/ D9 N
pipetest
0 m7 q5 p# F2 F" ]$ W# r' c% |) V8 v2 K
用户空间支持epoll的库 :
! P9 }7 ^9 l( Z: G
libevent
$ V* Z k: w; \/ g3 z4 n
ivykis
# k9 o6 R# ~" p# I7 X2 H2 Z在测试epoll过程中,做的一个 thttpd 的补丁:
1 [0 G' S# E$ L$ `/ Ythttpd.epoll.diff
5 L' w0 I# z. E0 [: y; Q结论:
9 K& b i6 x0 w+ x- O
这些数据显示/dev/epoll ( and sys_epoll )提高了服务器的效率(从应答报文速率和CPU利用率角度看)。/dev/epoll的应答速率完全独立于死连接的数目,而标准poll()和旧的 /dev/poll似乎不可避免的受到影响。和标准poll()以及旧的/dev/poll相比/dev/epoll的方差也明显小的多,这不尽使我觉得 (1) 将有跟多的处理能力(2)在高负栽下有可以预见的应答速率。RT signals和RT one-sig 是现在死连接变化时也基本保持不变,并且one-sig大约比简单RT signals快10-12% 。RT singnals实现 (即使 one-sig 较少)似乎不能适用于大量突发请求模拟真实因特网的情况,在这种情况下有大量连接处于active状态。这是因为RT signals队列的限制,即使打了one-sig的补丁, 也很容易填满队列。
1 j# z2 @2 |; \, l8 t
链接:
' d0 D( n8 a' |8 F9 X7 v
[1] The epoll scalability page at lse.
2 }4 Q4 a K7 l7 s[2] David Weekly - /dev/epoll Page
S* a* h- J1 S6 D1 T
& I: S, @+ ^* t$ y, L! J0 Y/ Y: R
References:
( U6 ~+ t* s9 j( `; w. U[1] W. Richard Stevens - "UNIX Network Programming, Volume I: Networking APIs: Sockets and XTI, 2nd edition"
6 z6 a8 p" w, v# ?
Prentice Hall, 1998.
; N+ ^0 [* r5 l% J" I8 V9 S5 Q
[2] W. Richard Stevens - "TCP/IP Illustrated, Volume 1: The Protocols"
0 b1 h4 w0 {6 c1 {Addison Wesley professional computing series, 1994.
" c" I' S) m# y& r# o5 W+ S# C! U[3] G. Banga and J. C. Mogul - "Scalable Kernel Performance for Internet Servers Under Realistic Load"
% P& u3 z0 E+ nProceedings of the USENIX Annual Technical Conference, June 1998.
' z2 A8 W+ c8 W! ?5 F$ G
[4] G. Banga. P. Druschel. J. C. Mogul - "Better Operating System Features for Faster Network Servers"
3 ]* Z& n, d& Z/ d. C5 \SIGMETRICS Workshop on Internet Server Performance, June 1998.
( v! \7 i2 `: u6 q- Q[5] G. Banga and P. Druschel - "Measuring the Capacity of a Web Server"
0 S. Y0 F8 n% \2 O# j3 pProceedings of the USENIX Symposium on Internet Technologies and Systems, December 1997.
7 L7 L$ F* _. n" }- C. L; u[6] Niels Provos and Charles Lever - "Scalable Network I/O in Linux"
) P1 o" t& `2 K. [
http://www.citi.umich.edu/techreports/reports/citi-tr-00-4.pdf
" o h' H) w( I* {) |- P* M* P[7] Dan Kegel - "The C10K problem"
0 y4 C, t( l9 z1 S+ R
http://www.kegel.com/c10k.html- o0 a1 a a% M0 f( a% m" C
[8] Richard Gooch - "IO Event Handling Under Linux"
; T# o8 |/ A7 |' g' H
http://www.atnf.csiro.au/~rgooch/linux/docs/io-events.html
3 T C: }8 V) a& s[9] Abhishek Chandra and David Mosberger - "Scalability of Linux Event-Dispatch Mechanisms"
1 \1 L5 L& K0 k* C! W; Xhttp://www.hpl.hp.com/techreports/2000/HPL-2000-174.html2 V3 ?4 q4 U3 R/ K: j1 Q8 s
[10] Niels Provos and Charles Lever - "Analyzing the Overload Behaviour of a Simple Web Server"
1 r' q' m/ B9 Z0 m* U/ T0 p3 E2 L; Yhttp://www.citi.umich.edu/techreports/reports/citi-tr-00-7.ps.gz
% B+ t7 I+ R5 H) }[11] D. Mosberger and T. Jin - "httperf -- A Tool for Measuring Web Server Performance"
. E# H# b9 n) c+ v! X7 JSIGMETRICS Workshop on Internet Server Performance, June 1998.