Timeouts are now very fast to look up - almost O(1) when there are few,
otherwise a much shorter list is traversed rather than all of them.
This is achieved by storing the elements by index into a circular array
as the time to expiry modulo the wheel size.  Each spoke of the wheel
stores a short queue containing the elements that match the expiry time.
Therefore only timeouts that are set to likely expire at elapsed_ticks
are checked during the clock interrupt, saving unnecessary processing time.

This speeds up boot time noticably and therefore should be faster to run
generally.  The thread timers are stored within the thread structures
themselves, so there is no arbitrary limit on their number, however for
legacy timeouts there are NTIMERS == 20 currently allocated statically
for things like comtimer (serial driver) which are in a reusable array.

TESTED: On Hurd i386 UP+apic

---
 device/chario.c      |   6 +-
 device/tty.h         |   2 +
 i386/i386at/ioapic.c |   2 +-
 kern/mach_clock.c    | 276 +++++++++++++++++++++++--------------------
 kern/mach_clock.h    |  37 +++---
 kern/sched_prim.c    |   4 +-
 kern/thread.h        |   4 +-
 7 files changed, 178 insertions(+), 153 deletions(-)

diff --git a/device/chario.c b/device/chario.c
index efb55867..660468f6 100644
--- a/device/chario.c
+++ b/device/chario.c
@@ -880,7 +880,7 @@ static void ttypush(void * _tp)
            if (state & TS_MIN_TO_RCV)
              { /* a character was received */
                tp->t_state = state & ~TS_MIN_TO_RCV;
-               timeout(ttypush, tp, pdma_timeouts[tp->t_ispeed]);
+               tp->t_timeout = timeout(ttypush, tp, 
pdma_timeouts[tp->t_ispeed]);
              }
            else
              {
@@ -932,7 +932,7 @@ void ttyinput(
           */
          if (tp->t_state & TS_MIN_TO) {
            tp->t_state &= ~(TS_MIN_TO|TS_MIN_TO_RCV);
-           untimeout(ttypush, tp);
+           untimeout(tp->t_timeout);
          }
          tty_queue_completion(&tp->t_delayed_read);
       }
@@ -952,7 +952,7 @@ void ttyinput(
            if ((tp->t_state & TS_MIN_TO) == 0)
              {
                tp->t_state |= TS_MIN_TO;
-               timeout(ttypush, tp, ptime);
+               tp->t_timeout = timeout(ttypush, tp, ptime);
              }
            else
              {
diff --git a/device/tty.h b/device/tty.h
index 3f8b2f63..e2239ea5 100644
--- a/device/tty.h
+++ b/device/tty.h
@@ -34,6 +34,7 @@
 #define        _DEVICE_TTY_H_
 
 #include <kern/lock.h>
+#include <kern/mach_clock.h>
 #include <kern/queue.h>
 #include <mach/port.h>
 
@@ -68,6 +69,7 @@ struct tty {
        queue_head_t    t_delayed_write;/* pending write requests */
        queue_head_t    t_delayed_open; /* pending open requests */
 
+       timeout_t       t_timeout;      /* timeout pointer */
 /*
  * Items beyond this point should be removed to device-specific
  * extension structures.
diff --git a/i386/i386at/ioapic.c b/i386/i386at/ioapic.c
index dba8e5e1..6d9e6db9 100644
--- a/i386/i386at/ioapic.c
+++ b/i386/i386at/ioapic.c
@@ -228,7 +228,7 @@ timer_measure_10x_apic_hz(void)
 {
     volatile int done = 0;
     uint32_t start = 0xffffffff;
-    timer_elt_data_t tmp_timer;
+    timeout_data_t tmp_timer;
     tmp_timer.fcn = timer_expiry_callback;
     tmp_timer.param = (void *)&done;
 
diff --git a/kern/mach_clock.c b/kern/mach_clock.c
index d7858a79..3c12ec39 100644
--- a/kern/mach_clock.c
+++ b/kern/mach_clock.c
@@ -65,12 +65,22 @@
 #endif
 
 #define MICROSECONDS_IN_ONE_SECOND 1000000
+#define TIMEOUT_WHEEL_BITS 8
+#define TIMEOUT_WHEEL_SIZE (1 << TIMEOUT_WHEEL_BITS)
+#define TIMEOUT_WHEEL_MASK (TIMEOUT_WHEEL_SIZE - 1)
+#define MAX_SOFTCLOCK_STEPS (TIMEOUT_WHEEL_SIZE >> 2)
 
 int            hz = HZ;                /* number of ticks per second */
 int            tick = (MICROSECONDS_IN_ONE_SECOND / HZ);       /* number of 
usec per tick */
 time_value64_t time = { 0, 0 };        /* wallclock time (unadjusted) */
 time_value64_t uptime = { 0, 0 };      /* time since bootup */
 unsigned long  elapsed_ticks = 0;      /* ticks elapsed since bootup */
+unsigned long  softticks = 0;          /* like elapsed_ticks but for 
softclock() */
+
+def_simple_lock_irq_data(static,       timeout_lock)   /* lock for accessing 
static list of timeouts */
+def_simple_lock_irq_data(static,       twheel_lock)    /* lock for accessing 
timeoutwheel of timeouts */
+queue_head_t   timeoutwheel[TIMEOUT_WHEEL_SIZE]; /* timeoutwheel of timeout 
lists */
+static timeout_t nextsoftcheck;                /* next timeout to be checked */
 
 int            timedelta = 0;
 int            tickdelta = 0;
@@ -82,6 +92,8 @@ unsigned      tickadj = 500 / HZ;     /* can adjust 100 usecs 
per second */
 #endif
 unsigned       bigadj = 1000000;       /* adjust 10*tickadj if adjustment
                                           > bigadj */
+#define NTIMERS        20
+timeout_data_t timeout_timers[NTIMERS] = {0};
 
 /* A high-precision (hardware) clock is taken into account to increase the
  * accuracy of the functions used for getting time (e.g. host_get_time64()).
@@ -161,9 +173,6 @@ MACRO_BEGIN                                                 
        \
        time_value64_add_hpc(uptime, _last_hpc);                        \
 MACRO_END
 
-def_simple_lock_irq_data(static,       timer_lock)     /* lock for ... */
-timer_elt_data_t       timer_head;     /* ordered list of timeouts */
-                                       /* (doubles as end-of-list) */
 
 /*
  *     Handle clock interrupts.
@@ -249,23 +258,20 @@ void clock_interrupt(
        if (my_cpu == master_cpu) {
 
            spl_t s;
-           timer_elt_t telt;
            boolean_t   needsoft = FALSE;
 
-
            /*
             *  Update the tick count since bootup, and handle
-            *  timeouts.
+            *  timeouts.
             */
 
-           s = simple_lock_irq(&timer_lock);
+           s = simple_lock_irq(&twheel_lock);
 
            elapsed_ticks++;
 
-           telt = (timer_elt_t)queue_first(&timer_head.chain);
-           if (telt->ticks <= elapsed_ticks)
-               needsoft = TRUE;
-           simple_unlock_irq(s, &timer_lock);
+           if (!queue_empty(&timeoutwheel[elapsed_ticks & TIMEOUT_WHEEL_MASK]))
+               needsoft = TRUE;
+           simple_unlock_irq(s, &twheel_lock);
 
            /*
             *  Increment the time-of-day clock.
@@ -310,122 +316,156 @@ void clock_interrupt(
                else {
                    setsoftclock();
                }
+           } else if (softticks + 1 == elapsed_ticks) {
+               ++softticks;
            }
        }
        last_hpc_read = hpclock_read_counter();
 }
 
-/*
- *     There is a nasty race between softclock and reset_timeout.
- *     For example, scheduling code looks at timer_set and calls
- *     reset_timeout, thinking the timer is set.  However, softclock
- *     has already removed the timer but hasn't called thread_timeout
- *     yet.
- *
- *     Interim solution:  We initialize timers after pulling
- *     them out of the queue, so a race with reset_timeout won't
- *     hurt.  The timeout functions (eg, thread_timeout,
- *     thread_depress_timeout) check timer_set/depress_priority
- *     to see if the timer has been cancelled and if so do nothing.
- *
- *     This still isn't correct.  For example, softclock pulls a
- *     timer off the queue, then thread_go resets timer_set (but
- *     reset_timeout does nothing), then thread_set_timeout puts the
- *     timer back on the queue and sets timer_set, then
- *     thread_timeout finally runs and clears timer_set, then
- *     thread_set_timeout tries to put the timer on the queue again
- *     and corrupts it.
- */
-
 void softclock(void)
 {
        /*
         *      Handle timeouts.
         */
+       register timeout_t t;
+       register queue_head_t *spoke;
+       register int steps; /* number of steps taken since we last allowed 
interrupts */
+       register unsigned long curticks;
        spl_t   s;
-       timer_elt_t     telt;
-       void    (*fcn)( void * param );
-       void    *param;
-
-       while (TRUE) {
-           s = simple_lock_irq(&timer_lock);
-           telt = (timer_elt_t) queue_first(&timer_head.chain);
-           if (telt->ticks > elapsed_ticks) {
-               simple_unlock_irq(s, &timer_lock);
-               break;
-           }
-           fcn = telt->fcn;
-           param = telt->param;
-
-           remqueue(&timer_head.chain, (queue_entry_t)telt);
-           telt->set = TELT_UNSET;
-           simple_unlock_irq(s, &timer_lock);
 
-           assert(fcn != 0);
-           (*fcn)(param);
+       steps = 0;
+       s = simple_lock_irq(&twheel_lock);
+       while (softticks != elapsed_ticks) {
+               ++softticks;
+               curticks = softticks;
+               spoke = &timeoutwheel[curticks & TIMEOUT_WHEEL_MASK];
+               t = NULL;
+               if (!queue_empty(spoke))
+                       t = (timeout_t)queue_next(spoke);
+               while (t) {
+                       if (t->t_time != curticks) {
+                               /* Cannot check tail via spoke because it's the 
wrong list */
+                               if (t->chain.next == 
(queue_entry_t)&timeoutwheel[t->t_time & TIMEOUT_WHEEL_MASK])
+                                       t = NULL;
+                               else
+                                       t = (timeout_t)queue_next(&t->chain);
+
+                               if (++steps >= MAX_SOFTCLOCK_STEPS) {
+                                       nextsoftcheck = t;
+                                       simple_unlock_irq(s, &twheel_lock); /* 
Give hardclock() a chance */
+                                       s = simple_lock_irq(&twheel_lock);
+                                       t = nextsoftcheck;
+                                       steps = 0;
+                               }
+                       } else {
+                               void (*t_fcn)(void *);
+                               void *t_arg;
+
+                               if (t->chain.next == spoke)
+                                       nextsoftcheck = NULL;
+                               else
+                                       nextsoftcheck = 
(timeout_t)queue_next(&t->chain);
+                               queue_remove(spoke, t, timeout_t, chain);
+                               t->chain.prev = t->chain.next = NULL;
+                               t_fcn = t->fcn;
+                               t_arg = t->param;
+                               if (t->set & TIMEOUT_ALLOC)
+                                       t->set = TIMEOUT_ALLOC;
+                               else
+                                       t->set &= ~TIMEOUT_PENDING;
+                               simple_unlock_irq(s, &twheel_lock);
+                               assert(t_fcn != 0);
+                               t_fcn(t_arg); /* call function at elapsed */
+                               s = simple_lock_irq(&twheel_lock);
+                               steps = 0;
+                               t = nextsoftcheck;
+                       }
+               }
        }
+       nextsoftcheck = NULL;
+       simple_unlock_irq(s, &twheel_lock);
 }
 
 /*
  *     Set timeout.
  *
  *     Parameters:
- *             telt     timer element.  Function and param are already set.
- *             interval time-out interval, in hz.
+ *             t        preallocated timeout. Function and param are already 
set.
+ *             interval time-out interval, in ticks.
  */
 void set_timeout(
-       timer_elt_t     telt,   /* already loaded */
+       timeout_t       t,      /* already loaded */
        unsigned int    interval)
 {
-       spl_t                   s;
-       timer_elt_t             next;
+       unsigned int index;
+       spl_t s;
 
-       s = simple_lock_irq(&timer_lock);
+       if (t->set & (TIMEOUT_ACTIVE | TIMEOUT_PENDING))
+               reset_timeout(t);
 
-       /* Start counting after next tick, to avoid partial ticks.  */
-       interval += elapsed_ticks + 1;
+       s = simple_lock_irq(&twheel_lock);
 
-       for (next = (timer_elt_t)queue_first(&timer_head.chain);
-            ;
-            next = (timer_elt_t)queue_next((queue_entry_t)next)) {
+       if (!interval)
+           interval = 1;
+
+       assert (!(t->set & (TIMEOUT_ACTIVE | TIMEOUT_PENDING)));
+
+       t->set |= TIMEOUT_ACTIVE | TIMEOUT_PENDING;
+       t->t_time = elapsed_ticks + interval;
+       index = t->t_time & TIMEOUT_WHEEL_MASK;
 
-           if (next->ticks > interval)
-               break;
-       }
-       telt->ticks = interval;
        /*
-        * Insert new timer element before 'next'
-        * (after 'next'->prev)
+        * Insert new timeout at the end of the corresponding wheel entry
         */
-       insque((queue_entry_t) telt, ((queue_entry_t)next)->prev);
-       telt->set = TELT_SET;
-       simple_unlock_irq(s, &timer_lock);
+       queue_enter(&timeoutwheel[index], t, timeout_t, chain);
+
+       simple_unlock_irq(s, &twheel_lock);
 }
 
-boolean_t reset_timeout(timer_elt_t telt)
+boolean_t reset_timeout(timeout_t t)
 {
-       spl_t   s;
+       queue_head_t *spoke;
+       spl_t s;
+
+       s = simple_lock_irq(&twheel_lock);
+       if (!(t->set & TIMEOUT_PENDING)) {
+               t->set &= ~TIMEOUT_ACTIVE;
+               simple_unlock_irq(s, &twheel_lock);
+               return FALSE;
+       }
+
+       t->set &= ~(TIMEOUT_PENDING | TIMEOUT_ACTIVE);
 
-       s = simple_lock_irq(&timer_lock);
-       if (telt->set) {
-           remqueue(&timer_head.chain, (queue_entry_t)telt);
-           telt->set = TELT_UNSET;
-           simple_unlock_irq(s, &timer_lock);
-           return TRUE;
+       spoke = &timeoutwheel[t->t_time & TIMEOUT_WHEEL_MASK];
+
+       if (nextsoftcheck == t) {
+               if (t->chain.next == spoke)
+                       nextsoftcheck = NULL;
+               else
+                       nextsoftcheck = (timeout_t)queue_next(&t->chain);
        }
-       else {
-           simple_unlock_irq(s, &timer_lock);
-           return FALSE;
+
+       if (!queue_empty(spoke)) {
+               queue_remove(spoke, t, timeout_t, chain);
+               t->chain.prev = t->chain.next = NULL;
+               simple_unlock_irq(s, &twheel_lock);
+               return TRUE;
        }
+       simple_unlock_irq(s, &twheel_lock);
+       return FALSE;
 }
 
 void init_timeout(void)
 {
-       simple_lock_init_irq(&timer_lock);
-       queue_init(&timer_head.chain);
-       timer_head.ticks = ~0;  /* MAXUINT - sentinel */
+       int i;
 
+       simple_lock_init_irq(&timeout_lock);
+       simple_lock_init_irq(&twheel_lock);
+       for (i = 0; i < TIMEOUT_WHEEL_SIZE; i++)
+               queue_init(&timeoutwheel[i]);
        elapsed_ticks = 0;
+       softticks = 0;
 }
 
 /*
@@ -677,10 +717,6 @@ void timeclose(dev_t dev, int flag)
  *     it can be called from interrupt handlers.
  */
 
-#define NTIMERS                20
-
-timer_elt_data_t timeout_timers[NTIMERS];
-
 /*
  *     Set timeout.
  *
@@ -688,51 +724,39 @@ timer_elt_data_t timeout_timers[NTIMERS];
  *     param:          parameter to pass to function
  *     interval:       timeout interval, in hz.
  */
-void timeout(
+timeout_t timeout(
        void    (*fcn)(void *param),
        void *  param,
        int     interval)
 {
-       spl_t   s;
-       timer_elt_t elt;
-
-       s = simple_lock_irq(&timer_lock);
-       for (elt = &timeout_timers[0]; elt < &timeout_timers[NTIMERS]; elt++)
-           if (elt->set == TELT_UNSET)
-               break;
-       if (elt == &timeout_timers[NTIMERS])
-           panic("timeout");
-       elt->fcn = fcn;
-       elt->param = param;
-       elt->set = TELT_ALLOC;
-       simple_unlock_irq(s, &timer_lock);
-
-       set_timeout(elt, (unsigned int)interval);
+       timeout_t t;
+       int i;
+       spl_t s;
+
+       s = simple_lock_irq(&timeout_lock);
+       for (i = 0; i < NTIMERS; i++) {
+               t = &timeout_timers[i];
+               if (!(t->set & TIMEOUT_ACTIVE))
+                       break;
+       }
+       if (i == NTIMERS)
+               panic("more than NTIMERS timeouts");
+
+       t->set |= TIMEOUT_ALLOC;
+       t->fcn = fcn;
+       t->param = param;
+       simple_unlock_irq(s, &timeout_lock);
+
+       set_timeout(t, interval);
+
+       return t;
 }
 
 /*
  * Returns a boolean indicating whether the timeout element was found
  * and removed.
  */
-boolean_t untimeout(void (*fcn)( void * param ), const void *param)
+boolean_t untimeout(timeout_t t)
 {
-       spl_t   s;
-       timer_elt_t elt;
-
-       s = simple_lock_irq(&timer_lock);
-       queue_iterate(&timer_head.chain, elt, timer_elt_t, chain) {
-
-           if ((fcn == elt->fcn) && (param == elt->param)) {
-               /*
-                *      Found it.
-                */
-               remqueue(&timer_head.chain, (queue_entry_t)elt);
-               elt->set = TELT_UNSET;
-
-               simple_unlock_irq(s, &timer_lock);
-               return (TRUE);
-           }
-       }
-       simple_unlock_irq(s, &timer_lock);
-       return (FALSE);
+       return reset_timeout(t);
 }
diff --git a/kern/mach_clock.h b/kern/mach_clock.h
index e83b638c..248aab7c 100644
--- a/kern/mach_clock.h
+++ b/kern/mach_clock.h
@@ -46,20 +46,20 @@ extern time_value64_t       uptime;         /* time since 
bootup */
 typedef void timer_func_t(void *);
 
 /* Time-out element.  */
-struct timer_elt {
-       queue_chain_t   chain;          /* chain in order of expiration */
+struct timeout {
+       queue_chain_t   chain;          /* chain of timeouts */
        timer_func_t    *fcn;           /* function to call */
        void *          param;          /* with this parameter */
-       unsigned long   ticks;          /* expiration time, in ticks */
-       int             set;            /* unset | set | allocated */
+       unsigned long   t_time;         /* expiration time, in ticks elapsed 
from boot */
+       unsigned char   set;            /* active | pending | allocated from 
pool */
 };
-#define        TELT_UNSET      0               /* timer not set */
-#define        TELT_SET        1               /* timer set */
-#define        TELT_ALLOC      2               /* timer allocated from pool */
 
-typedef        struct timer_elt        timer_elt_data_t;
-typedef        struct timer_elt        *timer_elt_t;
+typedef        struct timeout  timeout_data_t;
+typedef        struct timeout  *timeout_t;
 
+#define        TIMEOUT_ALLOC   0x1             /* timeout allocated from pool 
*/
+#define        TIMEOUT_ACTIVE  0x2             /* timeout active */
+#define        TIMEOUT_PENDING 0x4             /* timeout waiting for expiry */
 
 extern void clock_interrupt(
    int usec,
@@ -71,19 +71,18 @@ extern void softclock (void);
 
 /* For `private' timer elements.  */
 extern void set_timeout(
-   timer_elt_t telt,
+   timeout_t t,
    unsigned int interval);
-extern boolean_t reset_timeout(timer_elt_t telt);
+extern boolean_t reset_timeout(timeout_t t);
 
-#define        set_timeout_setup(telt,fcn,param,interval)      \
-       ((telt)->fcn = (fcn),                           \
-        (telt)->param = (param),                       \
-        (telt)->private = TRUE,                        \
-       set_timeout((telt), (interval)))
+#define        set_timeout_setup(t,fcn,param,interval)         \
+       ((t)->fcn = (fcn),                              \
+        (t)->param = (param),                          \
+       set_timeout((t), (interval)))
 
 #define        reset_timeout_check(t)                          \
        MACRO_BEGIN                                     \
-       if ((t)->set)                                   \
+       if ((t)->set & TIMEOUT_ACTIVE)                  \
            reset_timeout((t));                         \
        MACRO_END
 
@@ -104,8 +103,8 @@ extern void read_time_stamp (const time_value64_t *stamp, 
time_value64_t *result
 extern void mapable_time_init (void);
 
 /* For public timer elements.  */
-extern void timeout(timer_func_t *fcn, void *param, int interval);
-extern boolean_t untimeout(timer_func_t *fcn, const void *param);
+extern timeout_t timeout(timer_func_t *fcn, void *param, int interval);
+extern boolean_t untimeout(timeout_t t);
 
 extern int timeopen(dev_t dev, int flag, io_req_t ior);
 extern void timeclose(dev_t dev, int flag);
diff --git a/kern/sched_prim.c b/kern/sched_prim.c
index bcbfa160..5aa65811 100644
--- a/kern/sched_prim.c
+++ b/kern/sched_prim.c
@@ -67,7 +67,7 @@ unsigned      sched_tick;
 
 thread_t       sched_thread_id;
 
-timer_elt_data_t recompute_priorities_timer;
+timeout_data_t recompute_priorities_timer;
 
 /*
  *     State machine
@@ -185,7 +185,7 @@ static void thread_timeout(
        void *_thread)
 {
        thread_t thread = _thread;
-       assert(thread->timer.set == TELT_UNSET);
+       assert(!(thread->timer.set & TIMEOUT_PENDING));
 
        clear_wait(thread, THREAD_TIMED_OUT, FALSE);
 }
diff --git a/kern/thread.h b/kern/thread.h
index 0702e1b4..e5128dad 100644
--- a/kern/thread.h
+++ b/kern/thread.h
@@ -212,8 +212,8 @@ struct thread {
        time_value64_t  creation_time;
 
        /* Time-outs */
-       timer_elt_data_t timer;         /* timer for thread */
-       timer_elt_data_t depress_timer; /* timer for priority depression */
+       timeout_data_t  timer;          /* timer for thread */
+       timeout_data_t  depress_timer;  /* timer for priority depression */
 
        /* Ast/Halt data structures */
        /* Defined above */
-- 
2.45.2



Reply via email to