]> granicus.if.org Git - pgbouncer/commitdiff
New tools: AA-tree and Jenkins hash.
authorMarko Kreen <markokr@gmail.com>
Fri, 19 Oct 2007 08:40:32 +0000 (08:40 +0000)
committerMarko Kreen <markokr@gmail.com>
Fri, 19 Oct 2007 08:40:32 +0000 (08:40 +0000)
Makefile
src/aatree.c [new file with mode: 0644]
src/aatree.h [new file with mode: 0644]
src/bouncer.h
src/hash.c [new file with mode: 0644]
src/hash.h [new file with mode: 0644]

index c96acba5aa26f554041f4688b36841cee30c9446..041a7fb1ad2f6e079e88057967cbfd6989419cf9 100644 (file)
--- 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 (file)
index 0000000..87427c8
--- /dev/null
@@ -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 <stddef.h>   /* 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 (file)
index 0000000..9335951
--- /dev/null
@@ -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);
+
index 9e6c0181b95f31afb53122570c4d745c8b052c3a..937d87b1e4bef76729130bd1f230205497976f80 100644 (file)
@@ -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 (file)
index 0000000..57e76b3
--- /dev/null
@@ -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 <sys/types.h>
+
+#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 (file)
index 0000000..6c85d29
--- /dev/null
@@ -0,0 +1,3 @@
+
+unsigned lookup_hash(const void *data, unsigned len);
+