unix: use a heap for timers
Replace the red-black tree with a heap. The most common operation that libuv performs on timers is looking up the first timer to expire. With a red-black tree, that operation is O(log n). With a heap, it's O(1).
This commit is contained in:
parent
fc40836f9a
commit
f17c535b73
@ -25,6 +25,7 @@ lib_LTLIBRARIES = libuv.la
|
||||
libuv_la_CFLAGS = @CFLAGS@
|
||||
libuv_la_LDFLAGS = -no-undefined -version-info 11:0:0
|
||||
libuv_la_SOURCES = src/fs-poll.c \
|
||||
src/heap-inl.h \
|
||||
src/inet.c \
|
||||
src/queue.h \
|
||||
src/uv-common.c \
|
||||
|
||||
@ -29,6 +29,7 @@ INCLUDES = include/stdint-msvc2008.h \
|
||||
include/uv-version.h \
|
||||
include/uv-win.h \
|
||||
include/uv.h \
|
||||
src/heap-inl.h \
|
||||
src/queue.h \
|
||||
src/uv-common.h \
|
||||
src/win/atomicops-inl.h \
|
||||
|
||||
@ -177,16 +177,16 @@ typedef struct {
|
||||
void* idle_handles[2]; \
|
||||
void* async_handles[2]; \
|
||||
struct uv__async async_watcher; \
|
||||
/* RB_HEAD(uv__timers, uv_timer_s) */ \
|
||||
struct uv__timers { \
|
||||
struct uv_timer_s* rbh_root; \
|
||||
} timer_handles; \
|
||||
struct { \
|
||||
void* min; \
|
||||
unsigned int nelts; \
|
||||
} timer_heap; \
|
||||
uint64_t timer_counter; \
|
||||
uint64_t time; \
|
||||
int signal_pipefd[2]; \
|
||||
uv__io_t signal_io_watcher; \
|
||||
uv_signal_t child_watcher; \
|
||||
int emfile_fd; \
|
||||
uint64_t timer_counter; \
|
||||
UV_PLATFORM_LOOP_FIELDS \
|
||||
|
||||
#define UV_REQ_TYPE_PRIVATE /* empty */
|
||||
@ -265,14 +265,8 @@ typedef struct {
|
||||
int pending; \
|
||||
|
||||
#define UV_TIMER_PRIVATE_FIELDS \
|
||||
/* RB_ENTRY(uv_timer_s) tree_entry; */ \
|
||||
struct { \
|
||||
struct uv_timer_s* rbe_left; \
|
||||
struct uv_timer_s* rbe_right; \
|
||||
struct uv_timer_s* rbe_parent; \
|
||||
int rbe_color; \
|
||||
} tree_entry; \
|
||||
uv_timer_cb timer_cb; \
|
||||
void* heap_node[3]; \
|
||||
uint64_t timeout; \
|
||||
uint64_t repeat; \
|
||||
uint64_t start_id;
|
||||
|
||||
238
src/heap-inl.h
Normal file
238
src/heap-inl.h
Normal file
@ -0,0 +1,238 @@
|
||||
/* Copyright (c) 2013, Ben Noordhuis <info@bnoordhuis.nl>
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef UV_SRC_HEAP_H_
|
||||
#define UV_SRC_HEAP_H_
|
||||
|
||||
#include <stddef.h> /* NULL */
|
||||
|
||||
#if defined(__GNUC__)
|
||||
# define HEAP_EXPORT(declaration) __attribute__((unused)) static declaration
|
||||
#else
|
||||
# define HEAP_EXPORT(declaration) static declaration
|
||||
#endif
|
||||
|
||||
struct heap_node {
|
||||
struct heap_node* left;
|
||||
struct heap_node* right;
|
||||
struct heap_node* parent;
|
||||
};
|
||||
|
||||
/* A binary min heap. The usual properties hold: the root is the lowest
|
||||
* element in the set, the height of the tree is at most log2(nodes) and
|
||||
* it's always a complete binary tree.
|
||||
*
|
||||
* The heap function try hard to detect corrupted tree nodes at the cost
|
||||
* of a minor reduction in performance. Compile with -DNDEBUG to disable.
|
||||
*/
|
||||
struct heap {
|
||||
struct heap_node* min;
|
||||
unsigned int nelts;
|
||||
};
|
||||
|
||||
/* Return non-zero if a < b. */
|
||||
typedef int (*heap_compare_fn)(const struct heap_node* a,
|
||||
const struct heap_node* b);
|
||||
|
||||
/* Public functions. */
|
||||
HEAP_EXPORT(void heap_init(struct heap* heap));
|
||||
HEAP_EXPORT(struct heap_node* heap_min(const struct heap* heap));
|
||||
HEAP_EXPORT(void heap_insert(struct heap* heap,
|
||||
struct heap_node* newnode,
|
||||
heap_compare_fn less_than));
|
||||
HEAP_EXPORT(void heap_remove(struct heap* heap,
|
||||
struct heap_node* node,
|
||||
heap_compare_fn less_than));
|
||||
HEAP_EXPORT(void heap_dequeue(struct heap* heap, heap_compare_fn less_than));
|
||||
|
||||
/* Implementation follows. */
|
||||
|
||||
HEAP_EXPORT(void heap_init(struct heap* heap)) {
|
||||
heap->min = NULL;
|
||||
heap->nelts = 0;
|
||||
}
|
||||
|
||||
HEAP_EXPORT(struct heap_node* heap_min(const struct heap* heap)) {
|
||||
return heap->min;
|
||||
}
|
||||
|
||||
/* Swap parent with child. Child moves closer to the root, parent moves away. */
|
||||
static void heap_node_swap(struct heap* heap,
|
||||
struct heap_node* parent,
|
||||
struct heap_node* child) {
|
||||
struct heap_node* sibling;
|
||||
struct heap_node t;
|
||||
|
||||
t = *parent;
|
||||
*parent = *child;
|
||||
*child = t;
|
||||
|
||||
parent->parent = child;
|
||||
if (child->left == child) {
|
||||
child->left = parent;
|
||||
sibling = child->right;
|
||||
} else {
|
||||
child->right = parent;
|
||||
sibling = child->left;
|
||||
}
|
||||
if (sibling != NULL)
|
||||
sibling->parent = child;
|
||||
|
||||
if (parent->left != NULL)
|
||||
parent->left->parent = parent;
|
||||
if (parent->right != NULL)
|
||||
parent->right->parent = parent;
|
||||
|
||||
if (child->parent == NULL)
|
||||
heap->min = child;
|
||||
else if (child->parent->left == parent)
|
||||
child->parent->left = child;
|
||||
else
|
||||
child->parent->right = child;
|
||||
}
|
||||
|
||||
HEAP_EXPORT(void heap_insert(struct heap* heap,
|
||||
struct heap_node* newnode,
|
||||
heap_compare_fn less_than)) {
|
||||
struct heap_node** parent;
|
||||
struct heap_node** child;
|
||||
unsigned int path;
|
||||
unsigned int n;
|
||||
unsigned int k;
|
||||
|
||||
newnode->left = NULL;
|
||||
newnode->right = NULL;
|
||||
newnode->parent = NULL;
|
||||
|
||||
/* Calculate the path from the root to the insertion point. This is a min
|
||||
* heap so we always insert at the left-most free node of the bottom row.
|
||||
*/
|
||||
path = 0;
|
||||
for (k = 0, n = 1 + heap->nelts; n >= 2; k += 1, n /= 2)
|
||||
path = (path << 1) | (n & 1);
|
||||
|
||||
/* Now traverse the heap using the path we calculated in the previous step. */
|
||||
parent = child = &heap->min;
|
||||
while (k > 0) {
|
||||
parent = child;
|
||||
if (path & 1)
|
||||
child = &(*child)->right;
|
||||
else
|
||||
child = &(*child)->left;
|
||||
path >>= 1;
|
||||
k -= 1;
|
||||
}
|
||||
|
||||
/* Insert the new node. */
|
||||
newnode->parent = *parent;
|
||||
*child = newnode;
|
||||
heap->nelts += 1;
|
||||
|
||||
/* Walk up the tree and check at each node if the heap property holds.
|
||||
* It's a min heap so parent < child must be true.
|
||||
*/
|
||||
while (newnode->parent != NULL && less_than(newnode, newnode->parent))
|
||||
heap_node_swap(heap, newnode->parent, newnode);
|
||||
}
|
||||
|
||||
HEAP_EXPORT(void heap_remove(struct heap* heap,
|
||||
struct heap_node* node,
|
||||
heap_compare_fn less_than)) {
|
||||
struct heap_node* smallest;
|
||||
struct heap_node** max;
|
||||
struct heap_node* child;
|
||||
unsigned int path;
|
||||
unsigned int k;
|
||||
unsigned int n;
|
||||
|
||||
if (heap->nelts == 0)
|
||||
return;
|
||||
|
||||
/* Calculate the path from the min (the root) to the max, the left-most node
|
||||
* of the bottom row.
|
||||
*/
|
||||
path = 0;
|
||||
for (k = 0, n = heap->nelts; n >= 2; k += 1, n /= 2)
|
||||
path = (path << 1) | (n & 1);
|
||||
|
||||
/* Now traverse the heap using the path we calculated in the previous step. */
|
||||
max = &heap->min;
|
||||
while (k > 0) {
|
||||
if (path & 1)
|
||||
max = &(*max)->right;
|
||||
else
|
||||
max = &(*max)->left;
|
||||
path >>= 1;
|
||||
k -= 1;
|
||||
}
|
||||
|
||||
heap->nelts -= 1;
|
||||
|
||||
/* Unlink the max node. */
|
||||
child = *max;
|
||||
*max = NULL;
|
||||
|
||||
if (child == node) {
|
||||
/* We're removing either the max or the last node in the tree. */
|
||||
if (child == heap->min) {
|
||||
heap->min = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Replace the to be deleted node with the max node. */
|
||||
child->left = node->left;
|
||||
child->right = node->right;
|
||||
child->parent = node->parent;
|
||||
|
||||
if (child->left != NULL) {
|
||||
child->left->parent = child;
|
||||
}
|
||||
|
||||
if (child->right != NULL) {
|
||||
child->right->parent = child;
|
||||
}
|
||||
|
||||
if (node->parent == NULL) {
|
||||
heap->min = child;
|
||||
} else if (node->parent->left == node) {
|
||||
node->parent->left = child;
|
||||
} else {
|
||||
node->parent->right = child;
|
||||
}
|
||||
|
||||
/* Walk down the subtree and check at each node if the heap property holds.
|
||||
* It's a min heap so parent < child must be true. If the parent is bigger,
|
||||
* swap it with the smallest child.
|
||||
*/
|
||||
for (;;) {
|
||||
smallest = child;
|
||||
if (child->left != NULL && less_than(smallest, child))
|
||||
smallest = child->left;
|
||||
if (child->right != NULL && less_than(smallest, child))
|
||||
smallest = child->right;
|
||||
if (smallest == child)
|
||||
break;
|
||||
heap_node_swap(heap, child, smallest);
|
||||
}
|
||||
}
|
||||
|
||||
HEAP_EXPORT(void heap_dequeue(struct heap* heap, heap_compare_fn less_than)) {
|
||||
heap_remove(heap, heap->min, less_than);
|
||||
}
|
||||
|
||||
#undef HEAP_EXPORT
|
||||
|
||||
#endif /* UV_SRC_HEAP_H_ */
|
||||
@ -22,6 +22,7 @@
|
||||
#include "uv.h"
|
||||
#include "tree.h"
|
||||
#include "internal.h"
|
||||
#include "heap-inl.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
@ -80,7 +81,7 @@ static int uv__loop_init(uv_loop_t* loop, int default_loop) {
|
||||
uv__signal_global_once_init();
|
||||
|
||||
memset(loop, 0, sizeof(*loop));
|
||||
RB_INIT(&loop->timer_handles);
|
||||
heap_init((struct heap*) &loop->timer_heap);
|
||||
QUEUE_INIT(&loop->wq);
|
||||
QUEUE_INIT(&loop->active_reqs);
|
||||
QUEUE_INIT(&loop->idle_handles);
|
||||
|
||||
@ -20,35 +20,41 @@
|
||||
|
||||
#include "uv.h"
|
||||
#include "internal.h"
|
||||
#include "heap-inl.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
static int uv__timer_cmp(const uv_timer_t* a, const uv_timer_t* b) {
|
||||
static int timer_less_than(const struct heap_node* ha,
|
||||
const struct heap_node* hb) {
|
||||
const uv_timer_t* a;
|
||||
const uv_timer_t* b;
|
||||
|
||||
a = container_of(ha, const uv_timer_t, heap_node);
|
||||
b = container_of(hb, const uv_timer_t, heap_node);
|
||||
|
||||
if (a->timeout < b->timeout)
|
||||
return -1;
|
||||
if (a->timeout > b->timeout)
|
||||
return 1;
|
||||
/*
|
||||
* compare start_id when both has the same timeout. start_id is
|
||||
* allocated with loop->timer_counter in uv_timer_start().
|
||||
if (b->timeout < a->timeout)
|
||||
return 0;
|
||||
|
||||
/* Compare start_id when both have the same timeout. start_id is
|
||||
* allocated with loop->timer_counter in uv_timer_start().
|
||||
*/
|
||||
if (a->start_id < b->start_id)
|
||||
return -1;
|
||||
if (a->start_id > b->start_id)
|
||||
return 1;
|
||||
if (b->start_id < a->start_id)
|
||||
return 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
RB_GENERATE_STATIC(uv__timers, uv_timer_s, tree_entry, uv__timer_cmp)
|
||||
|
||||
|
||||
int uv_timer_init(uv_loop_t* loop, uv_timer_t* handle) {
|
||||
uv__handle_init(loop, (uv_handle_t*)handle, UV_TIMER);
|
||||
handle->timer_cb = NULL;
|
||||
handle->repeat = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -72,7 +78,9 @@ int uv_timer_start(uv_timer_t* handle,
|
||||
/* start_id is the second index to be compared in uv__timer_cmp() */
|
||||
handle->start_id = handle->loop->timer_counter++;
|
||||
|
||||
RB_INSERT(uv__timers, &handle->loop->timer_handles, handle);
|
||||
heap_insert((struct heap*) &handle->loop->timer_heap,
|
||||
(struct heap_node*) &handle->heap_node,
|
||||
timer_less_than);
|
||||
uv__handle_start(handle);
|
||||
|
||||
return 0;
|
||||
@ -83,7 +91,9 @@ int uv_timer_stop(uv_timer_t* handle) {
|
||||
if (!uv__is_active(handle))
|
||||
return 0;
|
||||
|
||||
RB_REMOVE(uv__timers, &handle->loop->timer_handles, handle);
|
||||
heap_remove((struct heap*) &handle->loop->timer_heap,
|
||||
(struct heap_node*) &handle->heap_node,
|
||||
timer_less_than);
|
||||
uv__handle_stop(handle);
|
||||
|
||||
return 0;
|
||||
@ -114,15 +124,15 @@ uint64_t uv_timer_get_repeat(const uv_timer_t* handle) {
|
||||
|
||||
|
||||
int uv__next_timeout(const uv_loop_t* loop) {
|
||||
const struct heap_node* heap_node;
|
||||
const uv_timer_t* handle;
|
||||
uint64_t diff;
|
||||
|
||||
/* RB_MIN expects a non-const tree root. That's okay, it doesn't modify it. */
|
||||
handle = RB_MIN(uv__timers, (struct uv__timers*) &loop->timer_handles);
|
||||
|
||||
if (handle == NULL)
|
||||
heap_node = heap_min((const struct heap*) &loop->timer_heap);
|
||||
if (heap_node == NULL)
|
||||
return -1; /* block indefinitely */
|
||||
|
||||
handle = container_of(heap_node, const uv_timer_t, heap_node);
|
||||
if (handle->timeout <= loop->time)
|
||||
return 0;
|
||||
|
||||
@ -135,9 +145,15 @@ int uv__next_timeout(const uv_loop_t* loop) {
|
||||
|
||||
|
||||
void uv__run_timers(uv_loop_t* loop) {
|
||||
struct heap_node* heap_node;
|
||||
uv_timer_t* handle;
|
||||
|
||||
while ((handle = RB_MIN(uv__timers, &loop->timer_handles))) {
|
||||
for (;;) {
|
||||
heap_node = heap_min((struct heap*) &loop->timer_heap);
|
||||
if (heap_node == NULL)
|
||||
break;
|
||||
|
||||
handle = container_of(heap_node, uv_timer_t, heap_node);
|
||||
if (handle->timeout > loop->time)
|
||||
break;
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user