* [patch] epoll reduced (to 1) number of passes over the ready set ...
@ 2007-02-27 2:32 Davide Libenzi
2007-02-27 4:00 ` Linus Torvalds
2007-02-27 9:15 ` Eric Dumazet
0 siblings, 2 replies; 7+ messages in thread
From: Davide Libenzi @ 2007-02-27 2:32 UTC (permalink / raw)
To: Linux Kernel Mailing List; +Cc: Linus Torvalds, Andrew Morton
Epoll is doing multiple passes over the ready set at the moment, because
of the constraints over the f_op->poll() call. Looking at the code again,
I noticed that we already hold the epoll semaphore in read, and this
(together with other locking conditions that hold while doing an
epoll_wait()) can lead to a smarter way to "ship" events to userspace (in
a single pass). I added more (even) more comments to the code to explain
the conditions why certain operations are safe.
This is a stress application that can be used to test the new code. It
spwans multiple thread and call epoll_wait() and epoll_ctl() from many
threads. Stress tested on my dual Opteron 254 w/out any problems.
http://www.xmailserver.org/totalmess.c
This is not a benchmark, just something that tries to stress and exploit
possible problems with the new code.
- Davide
eventpoll.c | 174 ++++++++++++++++++++++--------------------------------------
1 file changed, 66 insertions(+), 108 deletions(-)
diff -Nru linux-2.6.20/fs/eventpoll.c linux-2.6.20.mod/fs/eventpoll.c
--- linux-2.6.20/fs/eventpoll.c 2007-02-04 10:44:54.000000000 -0800
+++ linux-2.6.20.mod/fs/eventpoll.c 2007-02-26 17:32:25.000000000 -0800
@@ -218,9 +218,6 @@
/* List header used to link this item to the "struct file" items list */
struct list_head fllink;
- /* List header used to link the item to the transfer list */
- struct list_head txlink;
-
/*
* This is used during the collection/transfer of events to userspace
* to pin items empty events set.
@@ -259,10 +256,9 @@
static int ep_eventpoll_close(struct inode *inode, struct file *file);
static unsigned int ep_eventpoll_poll(struct file *file, poll_table *wait);
static int ep_collect_ready_items(struct eventpoll *ep,
- struct list_head *txlist, int maxevents);
+ struct list_head *txlist);
static int ep_send_events(struct eventpoll *ep, struct list_head *txlist,
- struct epoll_event __user *events);
-static void ep_reinject_items(struct eventpoll *ep, struct list_head *txlist);
+ struct epoll_event __user *events, int maxevents);
static int ep_events_transfer(struct eventpoll *ep,
struct epoll_event __user *events,
int maxevents);
@@ -355,17 +351,6 @@
return rb_parent(n) != n;
}
-/*
- * Remove the item from the list and perform its initialization.
- * This is useful for us because we can test if the item is linked
- * using "ep_is_linked(p)".
- */
-static inline void ep_list_del(struct list_head *p)
-{
- list_del(p);
- INIT_LIST_HEAD(p);
-}
-
/* Tells us if the item is currently linked */
static inline int ep_is_linked(struct list_head *p)
{
@@ -480,7 +465,7 @@
epi = list_entry(lsthead->next, struct epitem, fllink);
ep = epi->ep;
- ep_list_del(&epi->fllink);
+ list_del_init(&epi->fllink);
down_write(&ep->sem);
ep_remove(ep, epi);
up_write(&ep->sem);
@@ -1011,7 +996,6 @@
ep_rb_initnode(&epi->rbn);
INIT_LIST_HEAD(&epi->rdllink);
INIT_LIST_HEAD(&epi->fllink);
- INIT_LIST_HEAD(&epi->txlink);
INIT_LIST_HEAD(&epi->pwqlist);
epi->ep = ep;
ep_set_ffd(&epi->ffd, tfile, fd);
@@ -1080,7 +1064,7 @@
*/
write_lock_irqsave(&ep->lock, flags);
if (ep_is_linked(&epi->rdllink))
- ep_list_del(&epi->rdllink);
+ list_del_init(&epi->rdllink);
write_unlock_irqrestore(&ep->lock, flags);
kmem_cache_free(epi_cache, epi);
@@ -1170,7 +1154,7 @@
while (!list_empty(lsthead)) {
pwq = list_entry(lsthead->next, struct eppoll_entry, llink);
- ep_list_del(&pwq->llink);
+ list_del_init(&pwq->llink);
remove_wait_queue(pwq->whead, &pwq->wait);
kmem_cache_free(pwq_cache, pwq);
}
@@ -1213,7 +1197,7 @@
* we want to remove it from this list to avoid stale events.
*/
if (ep_is_linked(&epi->rdllink))
- ep_list_del(&epi->rdllink);
+ list_del_init(&epi->rdllink);
error = 0;
eexit_1:
@@ -1248,7 +1232,7 @@
/* Remove the current item from the list of epoll hooks */
spin_lock(&file->f_ep_lock);
if (ep_is_linked(&epi->fllink))
- ep_list_del(&epi->fllink);
+ list_del_init(&epi->fllink);
spin_unlock(&file->f_ep_lock);
/* We need to acquire the write IRQ lock before calling ep_unlink() */
@@ -1363,46 +1347,19 @@
/*
* Since we have to release the lock during the __copy_to_user() operation and
- * during the f_op->poll() call, we try to collect the maximum number of items
- * by reducing the irqlock/irqunlock switching rate.
+ * during the f_op->poll() call, we acquire the ready list into our temporary
+ * transmission list. List splice is O(1).
*/
-static int ep_collect_ready_items(struct eventpoll *ep, struct list_head *txlist, int maxevents)
+static int ep_collect_ready_items(struct eventpoll *ep, struct list_head *txlist)
{
- int nepi;
unsigned long flags;
- struct list_head *lsthead = &ep->rdllist, *lnk;
- struct epitem *epi;
write_lock_irqsave(&ep->lock, flags);
-
- for (nepi = 0, lnk = lsthead->next; lnk != lsthead && nepi < maxevents;) {
- epi = list_entry(lnk, struct epitem, rdllink);
-
- lnk = lnk->next;
-
- /* If this file is already in the ready list we exit soon */
- if (!ep_is_linked(&epi->txlink)) {
- /*
- * This is initialized in this way so that the default
- * behaviour of the reinjecting code will be to push back
- * the item inside the ready list.
- */
- epi->revents = epi->event.events;
-
- /* Link the ready item into the transfer list */
- list_add(&epi->txlink, txlist);
- nepi++;
-
- /*
- * Unlink the item from the ready list.
- */
- ep_list_del(&epi->rdllink);
- }
- }
-
+ list_splice(&ep->rdllist, txlist);
+ INIT_LIST_HEAD(&ep->rdllist);
write_unlock_irqrestore(&ep->lock, flags);
- return nepi;
+ return 0;
}
@@ -1412,21 +1369,24 @@
* because of the way poll() is traditionally implemented in Linux.
*/
static int ep_send_events(struct eventpoll *ep, struct list_head *txlist,
- struct epoll_event __user *events)
+ struct epoll_event __user *events, int maxevents)
{
- int eventcnt = 0;
+ int eventcnt, error = -EFAULT, pwake = 0;
unsigned int revents;
- struct list_head *lnk;
+ unsigned long flags;
struct epitem *epi;
+ struct list_head injlist;
+
+ INIT_LIST_HEAD(&injlist);
/*
* We can loop without lock because this is a task private list.
* The test done during the collection loop will guarantee us that
* another task will not try to collect this file. Also, items
- * cannot vanish during the loop because we are holding "sem".
+ * cannot vanish during the loop because we are holding "sem" in read.
*/
- list_for_each(lnk, txlist) {
- epi = list_entry(lnk, struct epitem, txlink);
+ for (eventcnt = 0; !list_empty(txlist) && eventcnt < maxevents;) {
+ epi = list_entry(txlist->next, struct epitem, rdllink);
/*
* Get the ready file event set. We can safely use the file
@@ -1442,56 +1402,56 @@
*/
epi->revents = revents & epi->event.events;
+ /*
+ * Is the event mask intersect the caller-requested one,
+ * deliver the event to userspace. Again, we are holding
+ * "sem" in read, so no operations coming from userspace,
+ * that change the item can happen.
+ */
if (epi->revents) {
if (__put_user(epi->revents,
&events[eventcnt].events) ||
__put_user(epi->event.data,
&events[eventcnt].data))
- return -EFAULT;
+ goto errxit;
if (epi->event.events & EPOLLONESHOT)
epi->event.events &= EP_PRIVATE_BITS;
eventcnt++;
}
- }
- return eventcnt;
-}
-
-
-/*
- * Walk through the transfer list we collected with ep_collect_ready_items()
- * and, if 1) the item is still "alive" 2) its event set is not empty 3) it's
- * not already linked, links it to the ready list. Same as above, we are holding
- * "sem" so items cannot vanish underneath our nose.
- */
-static void ep_reinject_items(struct eventpoll *ep, struct list_head *txlist)
-{
- int ricnt = 0, pwake = 0;
- unsigned long flags;
- struct epitem *epi;
-
- write_lock_irqsave(&ep->lock, flags);
-
- while (!list_empty(txlist)) {
- epi = list_entry(txlist->next, struct epitem, txlink);
-
- /* Unlink the current item from the transfer list */
- ep_list_del(&epi->txlink);
/*
- * If the item is no more linked to the interest set, we don't
- * have to push it inside the ready list because the following
- * ep_release_epitem() is going to drop it. Also, if the current
- * item is set to have an Edge Triggered behaviour, we don't have
- * to push it back either.
+ * This is tricky. We are holding the "sem" in read, and this
+ * means that the operations that can change the "linked" status
+ * of the epoll item (epi->rbn and epi->rdllink) cannot touch them.
+ * Also, since we are "linked" from a epi->rdllink POV (the item
+ * is linked to our transmission list, we just splice'd), the
+ * ep_poll_callback() cannot touch us either, because of the check
+ * present in there. Another parallel epoll_wait() will not
+ * get the same result set, since we spliced the ready list before.
*/
- if (ep_rb_linked(&epi->rbn) && !(epi->event.events & EPOLLET) &&
- (epi->revents & epi->event.events) && !ep_is_linked(&epi->rdllink)) {
- list_add_tail(&epi->rdllink, &ep->rdllist);
- ricnt++;
- }
+ list_del(&epi->rdllink);
+ if (!(epi->event.events & EPOLLET) &&
+ (epi->revents & epi->event.events))
+ list_add_tail(&epi->rdllink, &injlist);
+ else {
+ /*
+ * Be sure the item is totally detached before re-init the
+ * list_head. After INIT_LIST_HEAD() is committed, the
+ * ep_poll_callback() can requeue the item again, but we
+ * don't care since we are already past it.
+ */
+ smp_mb();
+ INIT_LIST_HEAD(&epi->rdllink);
+ }
}
+ error = 0;
- if (ricnt) {
+ errxit:
+
+ write_lock_irqsave(&ep->lock, flags);
+ if (!list_empty(&injlist) || !list_empty(txlist)) {
+ list_splice(txlist, &ep->rdllist);
+ list_splice(&injlist, &ep->rdllist);
/*
* Wake up ( if active ) both the eventpoll wait list and the ->poll()
* wait list.
@@ -1500,14 +1460,15 @@
__wake_up_locked(&ep->wq, TASK_UNINTERRUPTIBLE |
TASK_INTERRUPTIBLE);
if (waitqueue_active(&ep->poll_wait))
- pwake++;
+ pwake++;
}
-
write_unlock_irqrestore(&ep->lock, flags);
/* We have to call this outside the lock */
if (pwake)
ep_poll_safewake(&psw, &ep->poll_wait);
+
+ return eventcnt == 0 ? error: eventcnt;
}
@@ -1517,7 +1478,7 @@
static int ep_events_transfer(struct eventpoll *ep,
struct epoll_event __user *events, int maxevents)
{
- int eventcnt = 0;
+ int eventcnt;
struct list_head txlist;
INIT_LIST_HEAD(&txlist);
@@ -1529,13 +1490,10 @@
down_read(&ep->sem);
/* Collect/extract ready items */
- if (ep_collect_ready_items(ep, &txlist, maxevents) > 0) {
- /* Build result set in userspace */
- eventcnt = ep_send_events(ep, &txlist, events);
-
- /* Reinject ready items into the ready list */
- ep_reinject_items(ep, &txlist);
- }
+ ep_collect_ready_items(ep, &txlist);
+
+ /* Build result set in userspace */
+ eventcnt = ep_send_events(ep, &txlist, events, maxevents);
up_read(&ep->sem);
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [patch] epoll reduced (to 1) number of passes over the ready set ...
2007-02-27 2:32 [patch] epoll reduced (to 1) number of passes over the ready set Davide Libenzi
@ 2007-02-27 4:00 ` Linus Torvalds
2007-02-27 4:14 ` Davide Libenzi
2007-02-27 9:15 ` Eric Dumazet
1 sibling, 1 reply; 7+ messages in thread
From: Linus Torvalds @ 2007-02-27 4:00 UTC (permalink / raw)
To: Davide Libenzi; +Cc: Linux Kernel Mailing List, Andrew Morton
On Mon, 26 Feb 2007, Davide Libenzi wrote:
>
> Epoll is doing multiple passes over the ready set at the moment, because
> of the constraints over the f_op->poll() call. Looking at the code again,
> I noticed that we already hold the epoll semaphore in read, and this
> (together with other locking conditions that hold while doing an
> epoll_wait()) can lead to a smarter way to "ship" events to userspace (in
> a single pass).
Ok, this looks fine, but I think it makes more sense with (a) a sign-off
line and perhaps a slightly expanded explanation, and (b) keeping it in
-mm until after 2.6.21 is out, and merging it then.
Yes? No? Linus is on some crazy drugs again?
Linus
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [patch] epoll reduced (to 1) number of passes over the ready set ...
2007-02-27 4:00 ` Linus Torvalds
@ 2007-02-27 4:14 ` Davide Libenzi
0 siblings, 0 replies; 7+ messages in thread
From: Davide Libenzi @ 2007-02-27 4:14 UTC (permalink / raw)
To: Linus Torvalds; +Cc: Linux Kernel Mailing List, Andrew Morton
On Mon, 26 Feb 2007, Linus Torvalds wrote:
> On Mon, 26 Feb 2007, Davide Libenzi wrote:
> >
> > Epoll is doing multiple passes over the ready set at the moment, because
> > of the constraints over the f_op->poll() call. Looking at the code again,
> > I noticed that we already hold the epoll semaphore in read, and this
> > (together with other locking conditions that hold while doing an
> > epoll_wait()) can lead to a smarter way to "ship" events to userspace (in
> > a single pass).
>
> Ok, this looks fine, but I think it makes more sense with (a) a sign-off
> line and perhaps a slightly expanded explanation, and (b) keeping it in
> -mm until after 2.6.21 is out, and merging it then.
>
> Yes? No? Linus is on some crazy drugs again?
Let me do more testing. My post was to have more eyes looking at it. Once
I feel confident about it, and if no objections come from review, I'll
sign and repost.
- Davide
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [patch] epoll reduced (to 1) number of passes over the ready set ...
2007-02-27 2:32 [patch] epoll reduced (to 1) number of passes over the ready set Davide Libenzi
2007-02-27 4:00 ` Linus Torvalds
@ 2007-02-27 9:15 ` Eric Dumazet
2007-02-27 16:03 ` Davide Libenzi
1 sibling, 1 reply; 7+ messages in thread
From: Eric Dumazet @ 2007-02-27 9:15 UTC (permalink / raw)
To: Davide Libenzi; +Cc: Linux Kernel Mailing List, Linus Torvalds, Andrew Morton
On Tuesday 27 February 2007 03:32, Davide Libenzi wrote:
> Epoll is doing multiple passes over the ready set at the moment, because
> of the constraints over the f_op->poll() call. Looking at the code again,
> I noticed that we already hold the epoll semaphore in read, and this
> (together with other locking conditions that hold while doing an
> epoll_wait()) can lead to a smarter way to "ship" events to userspace (in
> a single pass). I added more (even) more comments to the code to explain
> the conditions why certain operations are safe.
> This is a stress application that can be used to test the new code. It
> spwans multiple thread and call epoll_wait() and epoll_ctl() from many
> threads. Stress tested on my dual Opteron 254 w/out any problems.
Davide,
This is really cool, because the size of epitem would fit now in 128 bytes
instead of 192 (on x86_64 platforms). So we also reduce memory usage.
I have one comment :
> */
> - list_for_each(lnk, txlist) {
> - epi = list_entry(lnk, struct epitem, txlink);
> + for (eventcnt = 0; !list_empty(txlist) && eventcnt < maxevents;) {
> + epi = list_entry(txlist->next, struct epitem, rdllink);
Now that we scan the rdllist list once, it may be usefull to use a prefetch()
hint.
list_for_each() has one prefetch(pos->next) automatically included, but not
your open coded loop.
I suggest adding after epi = list_entry(txlist->next, struct epitem, rdllink);
prefetch(epi->rdllink.next);
Thank you
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [patch] epoll reduced (to 1) number of passes over the ready set ...
2007-02-27 9:15 ` Eric Dumazet
@ 2007-02-27 16:03 ` Davide Libenzi
2007-02-28 11:13 ` Eric Dumazet
0 siblings, 1 reply; 7+ messages in thread
From: Davide Libenzi @ 2007-02-27 16:03 UTC (permalink / raw)
To: Eric Dumazet; +Cc: Linux Kernel Mailing List, Linus Torvalds, Andrew Morton
On Tue, 27 Feb 2007, Eric Dumazet wrote:
> On Tuesday 27 February 2007 03:32, Davide Libenzi wrote:
> > Epoll is doing multiple passes over the ready set at the moment, because
> > of the constraints over the f_op->poll() call. Looking at the code again,
> > I noticed that we already hold the epoll semaphore in read, and this
> > (together with other locking conditions that hold while doing an
> > epoll_wait()) can lead to a smarter way to "ship" events to userspace (in
> > a single pass). I added more (even) more comments to the code to explain
> > the conditions why certain operations are safe.
> > This is a stress application that can be used to test the new code. It
> > spwans multiple thread and call epoll_wait() and epoll_ctl() from many
> > threads. Stress tested on my dual Opteron 254 w/out any problems.
>
> Davide,
>
> This is really cool, because the size of epitem would fit now in 128 bytes
> instead of 192 (on x86_64 platforms). So we also reduce memory usage.
Yeah, I forgot to mention that I removed the txlink member.
> I have one comment :
>
> > */
> > - list_for_each(lnk, txlist) {
> > - epi = list_entry(lnk, struct epitem, txlink);
> > + for (eventcnt = 0; !list_empty(txlist) && eventcnt < maxevents;) {
> > + epi = list_entry(txlist->next, struct epitem, rdllink);
>
> Now that we scan the rdllist list once, it may be usefull to use a prefetch()
> hint.
>
> list_for_each() has one prefetch(pos->next) automatically included, but not
> your open coded loop.
>
> I suggest adding after epi = list_entry(txlist->next, struct epitem, rdllink);
> prefetch(epi->rdllink.next);
Will do in the next version, thx!
- Davide
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [patch] epoll reduced (to 1) number of passes over the ready set ...
2007-02-27 16:03 ` Davide Libenzi
@ 2007-02-28 11:13 ` Eric Dumazet
2007-02-28 15:29 ` Davide Libenzi
0 siblings, 1 reply; 7+ messages in thread
From: Eric Dumazet @ 2007-02-28 11:13 UTC (permalink / raw)
To: Davide Libenzi; +Cc: Linux Kernel Mailing List, Linus Torvalds, Andrew Morton
On Tuesday 27 February 2007 17:03, Davide Libenzi wrote:
> On Tue, 27 Feb 2007, Eric Dumazet wrote:
> > On Tuesday 27 February 2007 03:32, Davide Libenzi wrote:
> > > Epoll is doing multiple passes over the ready set at the moment,
> > > because of the constraints over the f_op->poll() call. Looking at the
> > > code again, I noticed that we already hold the epoll semaphore in read,
> > > and this (together with other locking conditions that hold while doing
> > > an epoll_wait()) can lead to a smarter way to "ship" events to
> > > userspace (in a single pass). I added more (even) more comments to the
> > > code to explain the conditions why certain operations are safe.
> > > This is a stress application that can be used to test the new code. It
> > > spwans multiple thread and call epoll_wait() and epoll_ctl() from many
> > > threads. Stress tested on my dual Opteron 254 w/out any problems.
> >
> > Davide,
> >
> > This is really cool, because the size of epitem would fit now in 128
> > bytes instead of 192 (on x86_64 platforms). So we also reduce memory
> > usage.
>
> Yeah, I forgot to mention that I removed the txlink member.
I am pretty sure you can also remove revents member from epitem.
It would greatly benefit to 32bits platforms, because resulting size would be
64 bytes instead of 68 (so a 50 % reduction because of 64 bytes alignment)
Eric
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [patch] epoll reduced (to 1) number of passes over the ready set ...
2007-02-28 11:13 ` Eric Dumazet
@ 2007-02-28 15:29 ` Davide Libenzi
0 siblings, 0 replies; 7+ messages in thread
From: Davide Libenzi @ 2007-02-28 15:29 UTC (permalink / raw)
To: Eric Dumazet; +Cc: Linux Kernel Mailing List, Linus Torvalds, Andrew Morton
On Wed, 28 Feb 2007, Eric Dumazet wrote:
> On Tuesday 27 February 2007 17:03, Davide Libenzi wrote:
> > On Tue, 27 Feb 2007, Eric Dumazet wrote:
> > > On Tuesday 27 February 2007 03:32, Davide Libenzi wrote:
> > > > Epoll is doing multiple passes over the ready set at the moment,
> > > > because of the constraints over the f_op->poll() call. Looking at the
> > > > code again, I noticed that we already hold the epoll semaphore in read,
> > > > and this (together with other locking conditions that hold while doing
> > > > an epoll_wait()) can lead to a smarter way to "ship" events to
> > > > userspace (in a single pass). I added more (even) more comments to the
> > > > code to explain the conditions why certain operations are safe.
> > > > This is a stress application that can be used to test the new code. It
> > > > spwans multiple thread and call epoll_wait() and epoll_ctl() from many
> > > > threads. Stress tested on my dual Opteron 254 w/out any problems.
> > >
> > > Davide,
> > >
> > > This is really cool, because the size of epitem would fit now in 128
> > > bytes instead of 192 (on x86_64 platforms). So we also reduce memory
> > > usage.
> >
> > Yeah, I forgot to mention that I removed the txlink member.
>
> I am pretty sure you can also remove revents member from epitem.
>
> It would greatly benefit to 32bits platforms, because resulting size would be
> 64 bytes instead of 68 (so a 50 % reduction because of 64 bytes alignment)
Yes indee. That does not need to exist anymore, once the de-coupled loop
is gone. Thx, I'll make a new patch later today.
- Davide
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2007-02-28 15:29 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-02-27 2:32 [patch] epoll reduced (to 1) number of passes over the ready set Davide Libenzi
2007-02-27 4:00 ` Linus Torvalds
2007-02-27 4:14 ` Davide Libenzi
2007-02-27 9:15 ` Eric Dumazet
2007-02-27 16:03 ` Davide Libenzi
2007-02-28 11:13 ` Eric Dumazet
2007-02-28 15:29 ` Davide Libenzi
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.