From: Marko Kreen Date: Fri, 19 Oct 2007 08:40:32 +0000 (+0000) Subject: New tools: AA-tree and Jenkins hash. X-Git-Tag: pgbouncer_1_2_rc2~145 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=13518f2d88acbd6ec9b109ee316e8d2a9a50a36d;p=pgbouncer New tools: AA-tree and Jenkins hash. --- diff --git a/Makefile b/Makefile index c96acba..041a7fb 100644 --- a/Makefile +++ b/Makefile @@ -2,10 +2,10 @@ # sources SRCS = client.c loader.c objects.c pooler.c proto.c sbuf.c server.c util.c \ admin.c stats.c takeover.c md5.c janitor.c pktbuf.c system.c main.c \ - varcache.c + varcache.c aatree.c hash.c HDRS = client.h loader.h objects.h pooler.h proto.h sbuf.h server.h util.h \ admin.h stats.h takeover.h md5.h janitor.h pktbuf.h system.h bouncer.h \ - list.h mbuf.h varcache.h + list.h mbuf.h varcache.h aatree.h hash.h # data & dirs to include in tgz DOCS = doc/overview.txt doc/usage.txt doc/config.txt doc/todo.txt diff --git a/src/aatree.c b/src/aatree.c new file mode 100644 index 0000000..87427c8 --- /dev/null +++ b/src/aatree.c @@ -0,0 +1,310 @@ +/* + * PgBouncer - Lightweight connection pooler for PostgreSQL. + * + * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ + * + * Permission to use, copy, modify, and 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. + */ + +/* + * Self-balancing binary tree. + * + * Here is an implementation of AA-tree (Arne Andersson tree) + * which is simplification of Red-Black tree. + * + * Red-black tree has following properties that must be kept: + * 1. A node is either red or black. + * 2. The root is black. + * 3. All leaves (NIL nodes) are black. + * 4. Both childen of red node are black. + * 5. Every path from root to leaf contains same number of black nodes. + * + * AA-tree adds additional property: + * 6. Red node can exist only as a right node. + * + * Red-black tree properties quarantee that the longest path is max 2x longer + * than shortest path (B-R-B-R-B-R-B vs. B-B-B-B) thus the tree will be roughly + * balanced. Also it has good worst-case guarantees for insertion and deletion, + * which makes it good tool for real-time applications. + * + * AA-tree removes most special cases from RB-tree, thus making resulting + * code lot simpler. It requires slightly more rotations when inserting + * and deleting but also keeps the tree more balanced. + */ + +#include /* for NULL */ + +#include "aatree.h" + +/* + * NIL node + */ +#define NIL (&_nil) +static struct Node _nil = { &_nil, &_nil, 0 }; + +/* + * Rebalancing. AA-tree needs only 2 operations + * to keep the tree balanced. + */ + +/* + * Fix red on left. + * + * X Y + * / --> \ + * Y X + * \ / + * a a + */ +static inline Node * skew(Node *x) +{ + Node *y = x->left; + if (x->level == y->level && x != NIL) { + x->left = y->right; + y->right = x; + return y; + } + return x; +} + +/* + * Fix 2 reds on right. + * + * X Y + * \ / \ + * Y --> X Z + * / \ \ + * a Z a + */ +static inline Node * split(Node *x) +{ + Node *y = x->right; + if (x->level == y->right->level && x != NIL) { + x->right = y->left; + y->left = x; + y->level++; + return y; + } + return x; +} + +/* insert is easy */ +static Node *rebalance_on_insert(Node *current) +{ + return split(skew(current)); +} + +/* remove is bit more tricky */ +static Node *rebalance_on_remove(Node *current) +{ + /* + * Removal can create a gap in levels, + * fix it by lowering current->level. + */ + if (current->left->level < current->level - 1 + || current->right->level < current->level - 1) + { + current->level--; + + /* if ->right is red, change it's level too */ + if (current->right->level > current->level) + current->right->level = current->level; + + /* reshape, ask Arne about those */ + current = skew(current); + current->right = skew(current->right); + current->right->right = skew(current->right->right); + current = split(current); + current->right = split(current->right); + } + return current; +} + +/* + * Recursive insertion + */ + +static Node * insert_sub(Tree *tree, Node *current, long value, Node *node) +{ + int cmp; + + if (current == NIL) { + /* + * Init node as late as possible, to avoid corrupting + * the tree in case it is already added. + */ + node->left = node->right = NIL; + node->level = 1; + + tree->count++; + return node; + } + + /* recursive insert */ + cmp = tree->node_cmp(value, current); + if (cmp > 0) + current->right = insert_sub(tree, current->right, value, node); + else if (cmp < 0) + current->left = insert_sub(tree, current->left, value, node); + else + /* already exists? */ + return current; + + return rebalance_on_insert(current); +} + +void tree_insert(Tree *tree, long value, Node *node) +{ + tree->root = insert_sub(tree, tree->root, value, node); +} + +/* + * Recursive removal + */ + +/* remove_sub could be used for that, but want to avoid comparisions */ +static Node *steal_leftmost(Tree *tree, Node *current, Node **save_p) +{ + if (current->left == NIL) { + *save_p = current; + return current->right; + } + + current->left = steal_leftmost(tree, current->left, save_p); + return rebalance_on_remove(current); +} + +/* drop this node from tree */ +static Node *drop_this_node(Tree *tree, Node *old) +{ + Node *new = NIL; + + if (old->left == NIL) + new = old->right; + else if (old->right == NIL) + new = old->left; + else { + /* + * Picking nearest from right is better than from left, + * due to asymmetry of the AA-tree. It will result in + * less tree operations in the long run, + */ + old->right = steal_leftmost(tree, old->right, &new); + + /* take old node's place */ + *new = *old; + } + + /* cleanup for old node */ + tree->release_cb(old, tree); + tree->count--; + + return new; +} + +static Node *remove_sub(Tree *tree, Node *current, long value) +{ + int cmp; + + /* not found? */ + if (current == NIL) + return current; + + cmp = tree->node_cmp(value, current); + if (cmp > 0) + current->right = remove_sub(tree, current->right, value); + else if (cmp < 0) + current->left = remove_sub(tree, current->left, value); + else + current = drop_this_node(tree, current); + + return rebalance_on_remove(current); +} + +void tree_remove(Tree *tree, long value) +{ + tree->root = remove_sub(tree, tree->root, value); +} + +/* + * Walking all nodes + */ + +static void walk_sub(Node *current, enum TreeWalkType wtype, + tree_walker_f walker, void *arg) +{ + if (current == NIL) + return; + + switch (wtype) { + case WALK_IN_ORDER: + walk_sub(current->left, wtype, walker, arg); + walker(current, arg); + walk_sub(current->right, wtype, walker, arg); + break; + case WALK_POST_ORDER: + walk_sub(current->left, wtype, walker, arg); + walk_sub(current->right, wtype, walker, arg); + walker(current, arg); + break; + case WALK_PRE_ORDER: + walker(current, arg); + walk_sub(current->left, wtype, walker, arg); + walk_sub(current->right, wtype, walker, arg); + break; + } +} + +/* walk tree in correct order */ +void tree_walk(Tree *tree, enum TreeWalkType wtype, tree_walker_f walker, void *arg) +{ + walk_sub(tree->root, wtype, walker, arg); +} + +/* walk tree in bottom-up order, so that walker can destroy the nodes */ +void tree_destroy(Tree *tree) +{ + walk_sub(tree->root, WALK_POST_ORDER, tree->release_cb, tree); + + /* reset tree */ + tree->root = NIL; + tree->count = 0; +} + +/* prepare tree */ +void tree_init(Tree *tree, tree_cmp_f cmpfn, tree_walker_f release_cb) +{ + tree->root = NIL; + tree->count = 0; + tree->node_cmp = cmpfn; + tree->release_cb = release_cb; +} + +/* + * search function + */ +Node *tree_search(Tree *tree, long value) +{ + Node *current = tree->root; + while (current != NIL) { + int cmp = tree->node_cmp(value, current); + if (cmp > 0) + current = current->right; + else if (cmp < 0) + current = current->left; + else + return current; + } + return NULL; +} + diff --git a/src/aatree.h b/src/aatree.h new file mode 100644 index 0000000..9335951 --- /dev/null +++ b/src/aatree.h @@ -0,0 +1,59 @@ +/* + * PgBouncer - Lightweight connection pooler for PostgreSQL. + * + * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ + * + * Permission to use, copy, modify, and 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. + */ + +typedef struct Node Node; +typedef struct Tree Tree; + +typedef int (*tree_cmp_f)(long, Node *node); +typedef void (*tree_walker_f)(Node *n, void *arg); + +/* + * Tree header, for storing helper functions. + */ +struct Tree { + Node *root; + int count; + tree_cmp_f node_cmp; + tree_walker_f release_cb; +}; + +/* + * Tree node. + */ +struct Node { + Node *left; /* smaller values */ + Node *right; /* larger values */ + int level; /* number of black nodes to leaf */ +}; + +/* + * walk order + */ +enum TreeWalkType { + WALK_IN_ORDER = 0, /* left->self->right */ + WALK_PRE_ORDER = 1, /* self->left->right */ + WALK_POST_ORDER = 2, /* left->right->self */ +}; + +void tree_init(Tree *tree, tree_cmp_f cmpfn, tree_walker_f release_cb); +Node *tree_search(Tree *tree, long value); +void tree_insert(Tree *tree, long value, Node *node); +void tree_remove(Tree *tree, long value); +void tree_walk(Tree *tree, enum TreeWalkType wtype, tree_walker_f walker, void *arg); +void tree_destroy(Tree *tree); + diff --git a/src/bouncer.h b/src/bouncer.h index 9e6c018..937d87b 100644 --- a/src/bouncer.h +++ b/src/bouncer.h @@ -66,6 +66,8 @@ typedef struct PgAddr PgAddr; typedef enum SocketState SocketState; typedef struct PktHdr PktHdr; +#include "aatree.h" +#include "hash.h" #include "util.h" #include "list.h" #include "mbuf.h" diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 0000000..57e76b3 --- /dev/null +++ b/src/hash.c @@ -0,0 +1,208 @@ +/* + * PgBouncer - Lightweight connection pooler for PostgreSQL. + * + * Copyright (c) 2007 Marko Kreen, Skype Technologies OÜ + * + * Permission to use, copy, modify, and 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. + */ + +/* + * A version of Bob Jenkins' lookup3.c hash. + * + * It is supposed to give same results as hashlittle() on little-endian + * and hashbig() on big-endian machines. + */ + +#include + +#include "hash.h" + +/* rotate uint32 */ +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +/* + * Disallow going over given data length. + * It is safe, if word boundary is not crossed, + * should be bit faster, although I have not noticed yet. + */ +#define STRICT_LENGTH 1 + +/* + * Bob Jenkins hash mixing functions for 3 32bit integers. + */ + +#define main_mix(a, b, c) do { \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} while (0) + +#define final_mix(a, b, c) do { \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c, 4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} while (0) + +/* + * Macros for fetching uint32_t from memory. + * + * Depending on alignment, it can be done with + * uint32, uint16 or uint8. + */ + +/* load uint from proper pointer and shift t oposition */ +#define GET(ptr, pos, sft) (((uint32_t)((ptr)[pos])) << sft) + +#ifdef WORDS_BIGENDIAN + +#define LOAD_BYTES(v, p8, n) do { \ + switch (n) { \ + case 4: v += GET(p8, 0, 24) | GET(p8, 1, 16) | GET(p8, 2, 8) | GET(p8, 3, 0); break; \ + case 3: v += GET(p8, 0, 24) | GET(p8, 1, 16) | GET(p8, 2, 8); break; \ + case 2: v += GET(p8, 0, 24) | GET(p8, 1, 16); break; \ + case 1: v += GET(p8, 0, 24); break; \ + } \ +} while (0) + +#define LOAD_SHORTS(v, p16, n) do { \ + switch (n) { \ + case 4: v += GET(p16, 0, 16) | GET(p16, 1, 0); break; \ + case 3: v += GET(p16, 0, 16) |(GET(p16, 1, 0) & 0xFF00); break; \ + case 2: v += GET(p16, 0, 16); break; \ + case 1: v += GET(p16, 0, 16) & 0xFF00; break; \ + } \ +} while (0) + +#define LOAD_INTS(v, p32, n) do { \ + switch (n) { \ + case 4: v += GET(p32, 0, 0); break; \ + case 3: v += GET(p32, 0, 0) & 0xFFFFFF00; break; \ + case 2: v += GET(p32, 0, 0) & 0xFFFF0000; break; \ + case 1: v += GET(p32, 0, 0) & 0xFF000000; break; \ + } \ +} while (0) + +#else /* LITTLE-ENDIAN */ + +#define LOAD_BYTES(v, p8, n) do { \ + switch (n) { \ + case 4: v += GET(p8, 0, 0) | GET(p8, 1, 8) | GET(p8, 2, 16) | GET(p8, 3, 24); break; \ + case 3: v += GET(p8, 0, 0) | GET(p8, 1, 8) | GET(p8, 2, 16); break; \ + case 2: v += GET(p8, 0, 0) | GET(p8, 1, 8); break; \ + case 1: v += GET(p8, 0, 0); break; \ + } \ +} while (0) + +#define LOAD_SHORTS(v, p16, n) do { \ + switch (n) { \ + case 4: v += GET(p16, 0, 0) | GET(p16, 1, 16); break; \ + case 3: v += GET(p16, 0, 0) |(GET(p16, 1, 16) & 0x00FF); break; \ + case 2: v += GET(p16, 0, 0); break; \ + case 1: v += GET(p16, 0, 0) & 0x00FF; break; \ + } \ +} while (0) + +#define LOAD_INTS(v, p32, n) do { \ + switch (n) { \ + case 4: v += GET(p32, 0, 0); break; \ + case 3: v += GET(p32, 0, 0) & 0x00FFFFFF; break; \ + case 2: v += GET(p32, 0, 0) & 0x0000FFFF; break; \ + case 1: v += GET(p32, 0, 0) & 0x000000FF; break; \ + } \ +} while (0) + +#endif /* LITTLE ENDIAN */ + +/* + * combined fetching, also increases data pointer. + */ +#define LOAD(v, data, n, unit) do { \ + if (n < 4 && unit > 1 && STRICT_LENGTH) { \ + LOAD_BYTES(v, ((uint8_t *)data), n); \ + } else { \ + switch (unit) { \ + case 4: LOAD_INTS(v, data, n); break; \ + case 2: LOAD_SHORTS(v, data, n); break; \ + case 1: LOAD_BYTES(v, data, n); break; \ + } \ + } \ + data += n/unit; \ +} while (0) + +/* + * common main loop + */ +#define main_loop(data, len, a, b, c, unit) do { \ + while (len > 12) { \ + LOAD(a, data, 4, unit); \ + LOAD(b, data, 4, unit); \ + LOAD(c, data, 4, unit); \ + main_mix(a, b, c); \ + len -= 12; \ + } \ +} while (0) + +/* + * fetch last 12 bytes into variables and mix them + */ +#define final_loop(data, len, a, b, c, unit) do { \ + switch (len) { \ + case 12: LOAD(a, data, 4, unit); LOAD(b, data, 4, unit); LOAD(c, data, 4, unit); break; \ + case 11: LOAD(a, data, 4, unit); LOAD(b, data, 4, unit); LOAD(c, data, 3, unit); break; \ + case 10: LOAD(a, data, 4, unit); LOAD(b, data, 4, unit); LOAD(c, data, 2, unit); break; \ + case 9: LOAD(a, data, 4, unit); LOAD(b, data, 4, unit); LOAD(c, data, 1, unit); break; \ + case 8: LOAD(a, data, 4, unit); LOAD(b, data, 4, unit); break; \ + case 7: LOAD(a, data, 4, unit); LOAD(b, data, 3, unit); break; \ + case 6: LOAD(a, data, 4, unit); LOAD(b, data, 2, unit); break; \ + case 5: LOAD(a, data, 4, unit); LOAD(b, data, 1, unit); break; \ + case 4: LOAD(a, data, 4, unit); break; \ + case 3: LOAD(a, data, 3, unit); break; \ + case 2: LOAD(a, data, 2, unit); break; \ + case 1: LOAD(a, data, 1, unit); break; \ + case 0: return c; \ + } \ + final_mix(a, b, c); \ +} while (0) + +/* + * common function body + */ +#define body(data, len, unit_type, unit) do { \ + uint32_t a, b, c; \ + const unit_type *ptr = data; \ + a = b = c = 0xdeadbeef + len; \ + main_loop(ptr, len, a, b, c, unit); \ + final_loop(ptr, len, a, b, c, unit); \ + return c; \ +} while (0) + +/* + * actual function + */ +unsigned lookup_hash(const void *data, unsigned len) +{ + if (((long)data & 3) == 0) + body(data, len, uint32_t, 4); + else if (((long)data & 1) == 0) + body(data, len, uint16_t, 2); + else + body(data, len, uint8_t, 1); +} + diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 0000000..6c85d29 --- /dev/null +++ b/src/hash.h @@ -0,0 +1,3 @@ + +unsigned lookup_hash(const void *data, unsigned len); +