diff options
Diffstat (limited to 'contrib/timer-wheel')
-rw-r--r-- | contrib/timer-wheel/find_last_bit.c | 82 | ||||
-rw-r--r-- | contrib/timer-wheel/timer-wheel.c | 264 | ||||
-rw-r--r-- | contrib/timer-wheel/timer-wheel.h | 71 |
3 files changed, 417 insertions, 0 deletions
diff --git a/contrib/timer-wheel/find_last_bit.c b/contrib/timer-wheel/find_last_bit.c new file mode 100644 index 00000000000..054e90a076f --- /dev/null +++ b/contrib/timer-wheel/find_last_bit.c @@ -0,0 +1,82 @@ +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +/** + * @find_last_bit + * optimized implementation of find last bit in + */ + +#ifndef BITS_PER_LONG +#define BITS_PER_LONG 64 +#endif + +static inline int fls(int x) +{ + int r = 32; + + if (!x) + return 0; + if (!(x & 0xffff0000u)) { + x <<= 16; + r -= 16; + } + if (!(x & 0xff000000u)) { + x <<= 8; + r -= 8; + } + if (!(x & 0xf0000000u)) { + x <<= 4; + r -= 4; + } + if (!(x & 0xc0000000u)) { + x <<= 2; + r -= 2; + } + if (!(x & 0x80000000u)) { + x <<= 1; + r -= 1; + } + return r; +} + + +unsigned long gf_tw_find_last_bit(const unsigned long *addr, unsigned long size) +{ + unsigned long words; + unsigned long tmp; + + /* Start at final word. */ + words = size / BITS_PER_LONG; + + /* Partial final word? */ + if (size & (BITS_PER_LONG-1)) { + tmp = (addr[words] & (~0UL >> (BITS_PER_LONG + - (size & (BITS_PER_LONG-1))))); + if (tmp) + goto found; + } + + while (words) { + tmp = addr[--words]; + if (tmp) { +found: + return words * BITS_PER_LONG + fls(tmp); + } + } + + /* Not found */ + return size; +} diff --git a/contrib/timer-wheel/timer-wheel.c b/contrib/timer-wheel/timer-wheel.c new file mode 100644 index 00000000000..e80d83992bf --- /dev/null +++ b/contrib/timer-wheel/timer-wheel.c @@ -0,0 +1,264 @@ +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/time.h> + +#include "timer-wheel.h" + +static inline void +__gf_tw_add_timer (struct tvec_base *base, struct gf_tw_timer_list *timer) +{ + int i; + unsigned long idx; + unsigned long expires; + struct list_head *vec; + + expires = timer->expires; + + idx = expires - base->timer_sec; + + if (idx < TVR_SIZE) { + i = expires & TVR_MASK; + vec = base->tv1.vec + i; + } else if (idx < 1 << (TVR_BITS + TVN_BITS)) { + i = (expires >> TVR_BITS) & TVN_MASK; + vec = base->tv2.vec + i; + } else if (idx < 1 << (TVR_BITS + 2*TVN_BITS)) { + i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK; + vec = base->tv3.vec + i; + } else if (idx < 1 << (TVR_BITS + 3*TVN_BITS)) { + i = (expires >> (TVR_BITS + 2*TVN_BITS)) & TVN_MASK; + vec = base->tv4.vec + i; + } else if (idx < 0) { + vec = base->tv1.vec + (base->timer_sec & TVR_MASK); + } else { + i = (expires >> (TVR_BITS + 3*TVN_BITS)) & TVN_MASK; + vec = base->tv5.vec + i; + } + + list_add_tail (&timer->entry, vec); +} + +/* optimized find_last_bit() */ +unsigned long gf_tw_find_last_bit(const unsigned long *, unsigned long); + +static inline unsigned long +apply_slack(struct tvec_base *base, struct gf_tw_timer_list *timer) +{ + long delta; + unsigned long mask, expires, expires_limit; + + expires = timer->expires; + + delta = expires - base->timer_sec; + if (delta < 256) + return expires; + + expires_limit = expires + delta / 256; + mask = expires ^ expires_limit; + if (mask == 0) + return expires; + + int bit = gf_tw_find_last_bit (&mask, BITS_PER_LONG); + mask = (1UL << bit) - 1; + + expires_limit = expires_limit & ~(mask); + return expires_limit; +} + +static inline void +gf_tw_detach_timer (struct gf_tw_timer_list *timer) +{ + struct list_head *entry = &timer->entry; + + list_del (entry); +} + +static inline int +cascade (struct tvec_base *base, struct tvec *tv, int index) +{ + struct gf_tw_timer_list *timer, *tmp; + struct list_head tv_list; + + list_replace_init (tv->vec + index, &tv_list); + + list_for_each_entry_safe (timer, tmp, &tv_list, entry) { + __gf_tw_add_timer (base, timer); + } + + return index; +} + +#define INDEX(N) ((base->timer_sec >> (TVR_BITS + N * TVN_BITS)) & TVN_MASK) + +/** + * run expired timers + */ +static inline void +run_timers (struct tvec_base *base) +{ + unsigned long index, call_time; + struct gf_tw_timer_list *timer; + + struct list_head work_list; + struct list_head *head = &work_list; + + pthread_spin_lock (&base->lock); + { + index = base->timer_sec & TVR_MASK; + + if (!index && + (!cascade (base, &base->tv2, INDEX(0))) && + (!cascade (base, &base->tv3, INDEX(1))) && + (!cascade (base, &base->tv4, INDEX(2)))) + cascade (base, &base->tv5, INDEX(3)); + + call_time = base->timer_sec++; + list_replace_init (base->tv1.vec + index, head); + while (!list_empty(head)) { + void (*fn)(struct gf_tw_timer_list *, void *, unsigned long); + void *data; + + timer = list_first_entry (head, struct gf_tw_timer_list, entry); + fn = timer->function; + data = timer->data; + + gf_tw_detach_timer (timer); + fn (timer, data, call_time); + } + } + pthread_spin_unlock (&base->lock); + +} + +void *runner (void *arg) +{ + struct timeval tv = {0,}; + struct tvec_base *base = arg; + + while (1) { + run_timers (base); + + tv.tv_sec = 1; + tv.tv_usec = 0; + (void) select (0, NULL, NULL, NULL, &tv); + } + + return NULL; + +} + +/* interface */ + +/** + * Add a timer in the timer wheel + */ +void gf_tw_add_timer (struct tvec_base *base, struct gf_tw_timer_list *timer) +{ + pthread_spin_lock (&base->lock); + { + timer->expires += base->timer_sec; + timer->expires = apply_slack (base, timer); + __gf_tw_add_timer (base, timer); + } + pthread_spin_unlock (&base->lock); +} + +/** + * Remove a timer from the timer wheel + */ +void gf_tw_del_timer (struct gf_tw_timer_list *timer) +{ + gf_tw_detach_timer (timer); +} + +int gf_tw_cleanup_timers (struct tvec_base *base) +{ + int ret = 0; + void *res = NULL; + + /* terminate runner */ + ret = pthread_cancel (base->runner); + if (ret != 0) + goto error_return; + ret = pthread_join (base->runner, &res); + if (ret != 0) + goto error_return; + if (res != PTHREAD_CANCELED) + goto error_return; + + /* destroy lock */ + ret = pthread_spin_destroy (&base->lock); + if (ret != 0) + goto error_return; + + /* deallocated timer base */ + free (base); + return 0; + + error_return: + return -1; +} + +/** + * Initialize various timer wheel lists and spawn a thread that + * invokes run_timers() + */ +struct tvec_base *gf_tw_init_timers () +{ + int j = 0; + int ret = 0; + struct timeval tv = {0,}; + struct tvec_base *base = NULL; + + base = malloc (sizeof (*base)); + if (!base) + goto error_return; + + ret = pthread_spin_init (&base->lock, 0); + if (ret != 0) + goto error_dealloc; + + for (j = 0; j < TVN_SIZE; j++) { + INIT_LIST_HEAD (base->tv5.vec + j); + INIT_LIST_HEAD (base->tv4.vec + j); + INIT_LIST_HEAD (base->tv3.vec + j); + INIT_LIST_HEAD (base->tv2.vec + j); + } + + for (j = 0; j < TVR_SIZE; j++) { + INIT_LIST_HEAD (base->tv1.vec + j); + } + + ret = gettimeofday (&tv, 0); + if (ret < 0) + goto destroy_lock; + base->timer_sec = tv.tv_sec; + + ret = pthread_create (&base->runner, NULL, runner, base); + if (ret != 0) + goto destroy_lock; + return base; + + destroy_lock: + (void) pthread_spin_destroy (&base->lock); + error_dealloc: + free (base); + error_return: + return NULL; +} diff --git a/contrib/timer-wheel/timer-wheel.h b/contrib/timer-wheel/timer-wheel.h new file mode 100644 index 00000000000..74b8dfdff5e --- /dev/null +++ b/contrib/timer-wheel/timer-wheel.h @@ -0,0 +1,71 @@ +/* + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef __TIMER_WHEEL_H +#define __TIMER_WHEEL_H + +#include <pthread.h> + +#include "list.h" + +#define TVR_BITS 8 +#define TVN_BITS 6 +#define TVR_SIZE (1 << TVR_BITS) +#define TVN_SIZE (1 << TVN_BITS) +#define TVR_MASK (TVR_SIZE - 1) +#define TVN_MASK (TVN_SIZE - 1) + +#define BITS_PER_LONG 64 + +struct tvec { + struct list_head vec[TVN_SIZE]; +}; + +struct tvec_root { + struct list_head vec[TVR_SIZE]; +}; + +struct tvec_base { + pthread_spinlock_t lock; /* base lock */ + + pthread_t runner; /* run_timer() */ + + unsigned long timer_sec; /* time counter */ + + struct tvec_root tv1; + struct tvec tv2; + struct tvec tv3; + struct tvec tv4; + struct tvec tv5; +}; + +struct gf_tw_timer_list { + void *data; + unsigned long expires; + + /** callback routine */ + void (*function)(struct gf_tw_timer_list *, void *, unsigned long); + + struct list_head entry; +}; + +/** The API! */ +struct tvec_base *gf_tw_init_timers (); +int gf_tw_cleanup_timers (struct tvec_base *); +void gf_tw_add_timer (struct tvec_base *, struct gf_tw_timer_list *); +void gf_tw_del_timer (struct gf_tw_timer_list *); + +#endif |