The hashtable routines from hlink.c modified to have more generic names,
authorWayne Davison <wayned@samba.org>
Mon, 3 Sep 2007 04:46:57 +0000 (04:46 +0000)
committerWayne Davison <wayned@samba.org>
Mon, 3 Sep 2007 04:46:57 +0000 (04:46 +0000)
to support 2 sizes of key (32 and 64 bits), and to have a non-allocating
option for the find routine (returning NULL for no match).

hashtable.c [new file with mode: 0644]

diff --git a/hashtable.c b/hashtable.c
new file mode 100644 (file)
index 0000000..d1be1b8
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Routines to provide a memory-efficient hashtable.
+ *
+ * Copyright (C) 2007 Wayne Davison
+ *
+ * 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 3 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, visit the http://fsf.org website.
+ */
+
+#include "rsync.h"
+
+#define HASH_LOAD_LIMIT(size) ((size)*3/4)
+
+struct hashtable *hashtable_create(int size, int key64)
+{
+       struct hashtable *tbl;
+       int node_size = key64 ? sizeof (struct ht_int64_node )
+                             : sizeof (struct ht_int32_node);
+
+       /* Pick a power of 2 that can hold the requested size. */
+       if (size & (size-1) || size < 16) {
+               int req = size;
+               size = 16;
+               while (size < req)
+                       size *= 2;
+       }
+
+       if (!(tbl = new(struct hashtable))
+        || !(tbl->nodes = new_array0(char, size * node_size)))
+               out_of_memory("hashtable_create");
+       tbl->size = size;
+       tbl->entries = 0;
+       tbl->node_size = node_size;
+
+       return tbl;
+}
+
+void hashtable_destroy(struct hashtable *tbl)
+{
+       free(tbl->nodes);
+       free(tbl);
+}
+
+/* This returns the node for the indicated key, either newly created or
+ * already existing.  Returns NULL if not allocating and not found. */
+void *hashtable_find(struct hashtable *tbl, int64 key, int allocate_if_missing)
+{
+       int key64 = (tbl->node_size > sizeof (struct ht_int32_node));
+       struct ht_int32_node *node;
+       uint32 ndx;
+
+       if (allocate_if_missing && tbl->entries > HASH_LOAD_LIMIT(tbl->size)) {
+               void *old_nodes = tbl->nodes;
+               int size = tbl->size * 2;
+               int i;
+
+               if (!(tbl->nodes = new_array0(char, size * tbl->node_size)))
+                       out_of_memory("hashtable_node");
+               tbl->size = size;
+               tbl->entries = 0;
+
+               for (i = size / 2; i-- > 0; ) {
+                       struct ht_int32_node *move_node = HT_NODE(tbl, old_nodes, i);
+                       int64 move_key = HT_KEY(move_node, key64);
+                       if (move_key == 0)
+                               continue;
+                       node = hashtable_find(tbl, move_key, 1);
+                       node->data = move_node->data;
+               }
+
+               free(old_nodes);
+       }
+
+       if (!key64) {
+               /* Based on Jenkins One-at-a-time hash. */
+               uchar buf[4], *keyp = buf;
+               int i;
+
+               SIVAL(buf, 0, key);
+               for (ndx = 0, i = 0; i < 4; i++) {
+                       ndx += keyp[i];
+                       ndx += (ndx << 10);
+                       ndx ^= (ndx >> 6);
+               }
+               ndx += (ndx << 3);
+               ndx ^= (ndx >> 11);
+               ndx += (ndx << 15);
+       } else {
+               /* Based on Jenkins hashword() from lookup3.c. */
+               uint32 a, b, c;
+
+               /* Set up the internal state */
+               a = b = c = 0xdeadbeef + (8 << 2);
+
+#define rot(x,k) (((x)<<(k)) ^ ((x)>>(32-(k))))
+               b += (uint32)(key >> 32);
+               a += (uint32)key;
+               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);
+#undef rot
+               ndx = c;
+       }
+
+       /* If it already exists, return the node.  If we're not
+        * allocating, return NULL if the key is not found. */
+       while (1) {
+               int64 nkey;
+
+               ndx &= tbl->size - 1;
+               node = HT_NODE(tbl, tbl->nodes, ndx);
+               nkey = HT_KEY(node, key64);
+
+               if (nkey == key)
+                       return node;
+               if (nkey == 0) {
+                       if (!allocate_if_missing)
+                               return NULL;
+                       break;
+               }
+               ndx++;
+       }
+
+       /* Take over this empty spot and then return the node. */
+       if (key64)
+               ((struct ht_int64_node*)node)->key = key;
+       else
+               node->key = key;
+       tbl->entries++;
+       return node;
+}