Browse Source

Use spinoff of rhashmap for hash map implementation.

Stefano Cossu 4 years ago
parent
commit
b6f9085397
5 changed files with 691 additions and 624 deletions
  1. 8 0
      include/core.h
  2. 2 3
      include/graph.h
  3. 112 21
      include/htable.h
  4. 146 169
      src/graph.c
  5. 423 431
      src/htable.c

+ 8 - 0
include/core.h

@@ -34,6 +34,13 @@
 
 # define UUIDSTR_SIZE 37
 
+// Handy flags operations.
+#define SET_FLAG(n, f) ((n) |= (f))
+#define CLR_FLAG(n, f) ((n) &= ~(f))
+#define TGL_FLAG(n, f) ((n) ^= (f))
+#define CHK_FLAG(n, f) ((n) & (f))
+
+
 
 /* * * RETURN CODES * * */
 
@@ -49,6 +56,7 @@
 
 #define LSUP_ERROR          (-88801)
 #define LSUP_PARSE_ERR      (-88802)
+#define LSUP_VALUE_ERR      (-88803)
 
 
 typedef size_t LSUP_Key;

+ 2 - 3
include/graph.h

@@ -1,8 +1,6 @@
 #ifndef _LSUP_GRAPH_H
 #define _LSUP_GRAPH_H
 
-#include "khash.h"
-
 #include "triple.h"
 
 
@@ -28,7 +26,8 @@ typedef struct Graph LSUP_Graph;
  *  by the callback.
 */
 typedef int (*keyset_match_fn_t)(
-        LSUP_Graph *src, LSUP_Graph *dest, khiter_t cur, void *ctx);
+        LSUP_Graph *src, LSUP_Graph *dest, const LSUP_TripleKey *spok,
+        void *ctx);
 
 
 int

+ 112 - 21
include/htable.h

@@ -23,55 +23,146 @@
 #ifndef _LSUP_HTABLE_H
 #define _LSUP_HTABLE_H
 
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-#include <limits.h>
+#include <inttypes.h>
+#include <stdbool.h>
 
 #include "include/core.h"
 
-#ifdef LSUP_BIG_HTABLE
-typedef size_t ht_size_t;
+// Max number of entries in the table. With HTABLE_BIG_SIZE, it is size_t.
+// Otherwise, UINT_MAX (4,294,967,295).
+#ifdef HTABLE_BIG_SIZE
+typedef size_t htsize_t;
 #else
-typedef uint32_t ht_size_t;
+typedef uint32_t htsize_t;
 #endif
 
+// Size of key entries. With HTABLE_BIG_KEY it is 65535 (64Kb). Otherwise,
+// it is 256 bytes.
+#ifdef HTABLE_BIG_KEY
+typedef uint16_t ksize_t;
+#else
+typedef uint8_t ksize_t;
+#endif
+
+// Size of value entries. With HTABLE_BIG_KEY it is 65535 (64Kb). Otherwise,
+// it is 256 bytes. For values that may be larger than 64 Kb, use pointers.
+#ifdef HTABLE_BIG_VAL
+typedef uint16_t vsize_t;
+#else
+typedef uint8_t vsize_t;
+#endif
+
+
+typedef enum {
+    HTABLE_NOCOPY           = 1 << 0,
+    HTABLE_IS_SET           = 1 << 1,
+} LSUP_HTFlag;
+
+
 /**
  * Key hashing function.
  *
- * Takes a void pointer. The key length is calculated from the ksize value in
- * the table.
+ * Takes a void pointer, a key length and a seed.
  */
-typedef uint64_t (*key_hash_fn_t)(const void *key);
+typedef uint64_t (*key_hash_fn_t)(
+        const void *key, ksize_t size, uint64_t seed);
 
 /**
  * Key equality function (true: keys are equal).
  *
- * Takes two void pointers. The key lengths are calculated from the ksize value
- * in the table.
+ * Takes two void pointers and a key length (which is constant within the
+ * hash table).
  */
-typedef bool (*key_eq_fn_t)(const void *a, const void *b);
+typedef bool (*key_eq_fn_t)(const void *a, const void *b, ksize_t size);
 
 /**
  * Hash table type.
  *
  * Supports up to UINT_MAX entries (~4 billions on most modern machines).
  *
- * If compiled with -DLSUP_BIG_HTABLE it supports up to size_t entries
+ * If compiled with -DHTABLE_BIG_SIZE it supports up to size_t entries
  * for extremely large in-memory graphs.
  */
-typedef struct HTable LSUP_HTable;
+typedef struct htable_t LSUP_HTable;
 
 extern int LSUP_htable_init(
-        LSUP_HTable *ht, ht_size_t size, uint32_t ksize, uint32_t vsize,
-        key_hash_fn_t key_hash_fn, key_eq_fn_t key_eq_fn);
+        LSUP_HTable *ht, htsize_t size, ksize_t ksize, vsize_t vsize,
+        key_hash_fn_t key_hash_fn, key_eq_fn_t key_eq_fn, unsigned flags);
 
-extern void LSUP_htable_done(LSUP_HTable *ht);
+extern LSUP_HTable *LSUP_htable_new(
+        htsize_t size, ksize_t ksize, vsize_t vsize,
+        key_hash_fn_t key_hash_fn, key_eq_fn_t key_eq_fn, unsigned flags);
+
+extern int LSUP_htable_resize(LSUP_HTable *ht, htsize_t newsize);
+
+extern htsize_t LSUP_htable_capacity(LSUP_HTable *ht);
+
+extern htsize_t LSUP_htable_size(LSUP_HTable *ht);
+
+extern int LSUP_htable_insert(LSUP_HTable *ht, const void *key, void *val);
+
+extern int LSUP_htable_put(LSUP_HTable *ht, const void *key, void *val);
+
+/**
+ * @brief Test the existence of a given key and find its value.
+ *
+ * @param LSUP_HTable ht[in]: Hash table or set.
+ *
+ * @param const void *key[in]: Key to look up.
+ *
+ * @param void **valp[out]: Pointer to be populated with the address of the
+ * value found at the key address, if any. If NULL is passed, or if the hash
+ * table is a set, the value is never populated. This pointer may be used as an
+ * lvalue.
+ *
+ * @return int: LSUP_OK if the key is found; LSUP_NORESULT if the key is not
+ *  found; a negative value on error.
+ */
+extern int LSUP_htable_get(
+        const LSUP_HTable *ht, const void *key, void **valp);
 
-extern ht_size_t LSUP_htable_get(const LSUP_HTable *ht, void *key);
+/*
+ * Remove the given key.
+ *
+ * @param LSUP_HTable ht[in]: Hash table or set.
+ *
+ * @param const void *key[in]: Key to remove.
+ *
+ * @return int: LSUP_OK if the key was removed; LSUP_NOACTION if it was not
+ *  found.
+ *
+ */
+extern int LSUP_htable_del(LSUP_HTable *ht, const void *key);
 
-extern ht_size_t LSUP_htable_put(LSUP_HTable *h, void *key, int *ret);
+/**
+ * Iterate over a hashmap or set.
+ *
+ * @param LSUP_HTable ht[in]: Hash table or set.
+ *
+ * @param htsize_t *cur[in]: an integer used as a cursor. Each successful
+ *  iteration of the function increases this value by 1. So the correct use
+ *  for this is to initialize a htsize_t variable to zero and passing its
+ *  pointer in a loop until necessary.
+ *
+ * @param void *key[out]: Pointer to be populated with the next key found.
+ *
+ * @param void **valp[out]: Pointer to the found value address. This can be
+ *  used as a normal lvalue. It may be NULL for sets or if the value is not
+ *  needed.
+ *
+ * @return int: LSUP_OK if the key is found; LSUP_END if the end of the data
+ *  is reached.
+ */
+extern int LSUP_htable_iter(
+        LSUP_HTable *ht, htsize_t *cur, void *key, void **valp);
+
+/*
+ * Free the memory used by the hash table.
+ *
+ * => It is the responsibility of the caller to remove elements if needed.
+ */
+extern void LSUP_htable_done(LSUP_HTable *ht);
 
-extern void LSUP_htable_del(LSUP_HTable *h, khint_t x);
+extern void LSUP_htable_free(LSUP_HTable *ht);
 
 #endif

+ 146 - 169
src/graph.c

@@ -1,3 +1,4 @@
+#include "htable.h"
 #include "graph.h"
 
 // Initial size of lookup graph. It will double each time capacity is reached.
@@ -23,49 +24,31 @@ enum {
 };
 
 /**
- * Index (identity) hashing function.
+ * Identity hashing function.
  *
  * Since the key is already a strong hash, reuse it for bucket allocation.
  */
-static inline khint64_t idx_key_hash_fn(LSUP_Key key)
-{ return key; }
+static inline uint64_t id_hash_fn(void *key, ksize_t size, uint64_t seed)
+{ return *(uint64_t*)key; }
+
 
 /**
- * Triple Key hash.
- *
- * Since triple keys are already hashes, interlace the first bytes of each
- * key element to preserve the individual identities.
+ * General XX64 hash. Strong (non-crypto) and extremely fast.
  */
-static inline khint64_t tkey_hash_fn(LSUP_TripleKey spok)
-{
-    khint64_t rc = (khint64_t)XXH64(spok, sizeof(LSUP_TripleKey), SEED);
-    TRACE("Key hash: 0x%lx\n", rc);
-    return rc;
-}
+static inline uint64_t xx64_hash_fn(
+        const void *key, ksize_t size, uint64_t seed)
+{ return XXH64(key, size, seed); }
 
-static inline int tkey_eq_fn(LSUP_Key *a, LSUP_Key *b)
-{
-    TRACE("a addr: %p", a);
-    TRACE("b addr: %p", b);
-    TRACE("Comparing a: {%lx, %lx, %lx}", a[0], a[1], a[2]);
-    TRACE("Wtih      b: {%lx, %lx, %lx}", b[0], b[1], b[2]);
-    // Evaluate from the least to the most probable to match.
-    return (
-            a[2] == b[2] &&
-            a[0] == b[0] &&
-            a[1] == b[1]);
-}
 
-KHASH_INIT(Keys, LSUP_Key *, char, 0, tkey_hash_fn, tkey_eq_fn)
+static inline bool buffer_eq_fn(const void *a, const void *b, ksize_t size)
+{ return memcmp(a, b, size); }
 
-KHASH_INIT(
-        Index, LSUP_Key, LSUP_Buffer, 1, idx_key_hash_fn, kh_int_hash_equal)
 
 typedef struct Graph {
     LSUP_store_type store_type;     // In-memory or MDB-backed
     LSUP_Term *uri;                 // Graph "name" (URI)
-    khash_t(Keys) *keys;
-    khash_t(Index) *idx;            // Dictionary of keys to serialized terms
+    LSUP_HTable *keys;
+    LSUP_HTable *idx;            // Dictionary of keys to serialized terms
 } Graph;
 
 /**
@@ -83,47 +66,67 @@ typedef bool (*LSUP_key_cmp_fn_t)(
         const LSUP_TripleKey* spok, const LSUP_Key k1, const LSUP_Key k2);
 
 
-// Dummy callback for queries with all parameters unbound. Returns true.
+/**
+ * Dummy callback for queries with all parameters unbound. Returns true.
 static bool lookup_none_cmp_fn(
         const LSUP_TripleKey* spok, const LSUP_Key k1, const LSUP_Key k2)
 { return true; }
+*/
 
-//  Keyset lookup for S key.
+/**
+ * Keyset lookup for S key.
+ */
 static bool lookup_sk_cmp_fn(
         const LSUP_TripleKey* spok, const LSUP_Key k1, const LSUP_Key k2)
 { return spok[0][0] == k1; }
 
-//  Keyset lookup for P key.
+/**
+ * Keyset lookup for P key.
+ */
 static bool lookup_pk_cmp_fn(
         const LSUP_TripleKey* spok, const LSUP_Key k1, const LSUP_Key k2)
 { return spok[0][1] == k1; }
 
-//  Keyset lookup for O key.
+/**
+ * Keyset lookup for O key.
+ */
 static bool lookup_ok_cmp_fn(
         const LSUP_TripleKey* spok, const LSUP_Key k1, const LSUP_Key k2)
 { return spok[0][2] == k1; }
 
-//  Keyset lookup for S and P keys.
+/**
+ * Keyset lookup for S and P keys.
+ */
 static bool lookup_skpk_cmp_fn(
         const LSUP_TripleKey* spok, const LSUP_Key k1, const LSUP_Key k2)
 { return spok[0][0] == k1 && spok[0][1] == k2; }
 
-//  Keyset lookup for S and O keys.
+/**
+ * Keyset lookup for S and O keys.
+ */
 static bool lookup_skok_cmp_fn(
         const LSUP_TripleKey* spok, const LSUP_Key k1, const LSUP_Key k2)
 { return spok[0][0] == k1 && spok[0][2] == k2; }
 
-//  Keyset lookup for P and O keys.
+/**
+ * Keyset lookup for P and O keys.
+ */
 static bool lookup_pkok_cmp_fn(
         const LSUP_TripleKey* spok, const LSUP_Key k1, const LSUP_Key k2)
 { return spok[0][1] == k1 && spok[0][2] == k2; }
 
 
-/* * * Post-lookup callbacks * * */
+/* * * Post-lookup callback prototypes * * */
+
+int match_add_fn(
+        LSUP_Graph *src, LSUP_Graph *dest, const LSUP_TripleKey *spok,
+        void *ctx);
+
 
-int match_add_fn(LSUP_Graph *src, LSUP_Graph *dest, khiter_t cur, void *ctx);
+int match_rm_fn(
+        LSUP_Graph *src, LSUP_Graph *dest, const LSUP_TripleKey *spok,
+        void *ctx);
 
-int match_rm_fn(LSUP_Graph *src, LSUP_Graph *dest, khiter_t cur, void *ctx);
 
 
 /* * * KEYSETS * * */
@@ -151,13 +154,14 @@ LSUP_graph_init(
         gr->uri = LSUP_term_new(LSUP_TERM_URI, uri_str, NULL, NULL);
     }
 
-    gr->keys = kh_init_Keys();
-    kh_resize_Keys(gr->keys, capacity);
+    gr->keys = LSUP_htable_new(
+            capacity, TRP_KLEN, 0, xx64_hash_fn, buffer_eq_fn, 0);
 
     switch (store_type ) {
         case LSUP_STORE_MEM:
-            gr->idx = kh_init_Index();
-            kh_resize_Index(gr->idx, capacity * IDX_SIZE_RATIO);
+            gr->idx = LSUP_htable_new(
+                capacity * IDX_SIZE_RATIO, sizeof(uint64_t), sizeof(uintptr_t),
+                xx64_hash_fn, buffer_eq_fn, 0);
             break;
 
         case LSUP_STORE_MDB:
@@ -196,7 +200,7 @@ static int graph_copy_contents(LSUP_Graph *src, LSUP_Graph *dest)
     trp.o = NULL;
 
     return LSUP_graph_match_callback(
-            src, dest, &trp, match_add_fn, true, NULL);
+            src, dest, &trp, &match_add_fn, true, NULL);
 }
 
 
@@ -212,15 +216,15 @@ LSUP_graph_copy(LSUP_Graph *dest, LSUP_Graph *src)
 int
 LSUP_graph_resize(LSUP_Graph *gr, size_t size)
 {
-    kh_resize_Keys(gr->keys, size);
-    kh_resize_Index(gr->idx, size * IDX_SIZE_RATIO);
+    LSUP_htable_resize(gr->keys, size);
+    LSUP_htable_resize(gr->idx, size * IDX_SIZE_RATIO);
 
     return LSUP_OK;
 }
 
 
 size_t
-LSUP_graph_capacity(LSUP_Graph *gr) { return kh_end(gr->keys); }
+LSUP_graph_capacity(LSUP_Graph *gr) { return LSUP_htable_capacity(gr->keys); }
 
 
 char *
@@ -228,7 +232,7 @@ LSUP_graph_uri(LSUP_Graph *gr) { return gr->uri->data; }
 
 
 size_t
-LSUP_graph_size(LSUP_Graph *gr) { return kh_size(gr->keys); }
+LSUP_graph_size(LSUP_Graph *gr) { return LSUP_htable_size(gr->keys); }
 
 
 int
@@ -243,31 +247,18 @@ LSUP_graph_add_triple(LSUP_Graph *gr, const LSUP_Triple *spo)
     LSUP_TripleKey spok = NULL_TRP;
 
     // Add term to index.
-    int status;
-    khiter_t cur;
     for (int i = 0; i < 3; i++) {
         spok[i] = LSUP_sterm_to_key(sspo + i);
 
-        cur = kh_put_Index(gr->idx, spok[i], &status);
-        if (status == HS_EMPTY || status == HS_TOMBSTONE) {
-            // If term is not indexed, store the struct in the index.
-            kh_value(gr->idx, cur) = sspo[i];
-        } else {
-            // If term is already in the index, discard and free it.
+        // If term is already in the index, discard and free it.
+        if (LSUP_htable_put(gr->idx, spok + i, sspo + i) == LSUP_NOACTION)
             LSUP_buffer_done(sspo + i);
-        }
     }
 
     // Add triple.
     TRACE("Inserting spok: {%lx, %lx, %lx}", spok[0], spok[1], spok[2]);
-    cur = kh_put_Keys(gr->keys, spok, &status);
-    //if (status == HS_EMPTY || status == HS_TOMBSTONE)
-    //    kh_key(gr->keys, cur) = spok;
-    TRACE("cur: %d\n", cur);
-    TRACE("Keyset size: %d\n", kh_size(gr->keys));
-    TRACE("Insert status: %d\n", status);
-
-    return status == HS_PRESENT ? LSUP_NOACTION : LSUP_OK;
+
+    return LSUP_htable_put(gr->keys, spok, NULL);
 }
 
 
@@ -294,13 +285,13 @@ LSUP_graph_add(LSUP_Graph *gr, const LSUP_Triple data[], size_t data_size)
 bool
 LSUP_graph_contains(const LSUP_Graph *gr, const LSUP_Triple *spo)
 {
-    LSUP_Key sk = LSUP_term_to_key(spo->s);
-    LSUP_Key pk = LSUP_term_to_key(spo->p);
-    LSUP_Key ok = LSUP_term_to_key(spo->o);
-
-    LSUP_TripleKey spok = {sk, pk, ok};
+    LSUP_TripleKey spok = {
+        LSUP_term_to_key(spo->s),
+        LSUP_term_to_key(spo->p),
+        LSUP_term_to_key(spo->o),
+    };
 
-    return kh_get_Keys(gr->keys, spok) != kh_end(gr->keys);
+    return LSUP_htable_get(gr->keys, spok, NULL) == LSUP_OK;
 }
 
 
@@ -308,25 +299,27 @@ int LSUP_graph_match_callback(
         LSUP_Graph *gr, LSUP_Graph *res, const LSUP_Triple *spo,
         keyset_match_fn_t callback_fn, bool match_cond, void *ctx)
 {
-    if (kh_size(gr->keys) == 0)
+    if (LSUP_htable_size(gr->keys) == 0)
         return LSUP_NOACTION;
 
+    htsize_t cur = 0;
     LSUP_Key k1, k2;
     LSUP_key_cmp_fn_t cmp_fn;
+    LSUP_TripleKey i_spok;
 
-    LSUP_Key sk = LSUP_term_to_key(spo->s);
-    LSUP_Key pk = LSUP_term_to_key(spo->p);
-    LSUP_Key ok = LSUP_term_to_key(spo->o);
-
-    if (sk != NULL_KEY && pk != NULL_KEY && ok != NULL_KEY) {
-        LSUP_TripleKey spok = {sk, pk, ok};
+    LSUP_TripleKey spok = {
+        LSUP_term_to_key(spo->s),
+        LSUP_term_to_key(spo->p),
+        LSUP_term_to_key(spo->o),
+    };
 
+    if (spok[0] != NULL_KEY && spok[1] != NULL_KEY && spok[2] != NULL_KEY) {
         if (match_cond == true) {
             // Shortcut for 3-term match—only if match_cond is true.
             LSUP_graph_init(res, 1, NULL, LSUP_STORE_MEM);
-            khint_t cur = kh_get_Keys(gr->keys, spok);
-            if(cur != kh_end(gr->keys)) {
-                callback_fn(gr, res, cur, ctx);
+            int rc = LSUP_htable_get(gr->keys, spok, NULL);
+            if(rc == LSUP_OK) {
+                callback_fn(gr, res, &spok, ctx);
                 return LSUP_OK;
             } else {
                 return LSUP_NOACTION;
@@ -335,30 +328,29 @@ int LSUP_graph_match_callback(
             // For negative condition (i.e. "apply this function to all triples
             // except the matching one")
             int rc = LSUP_NOACTION;
-            for (khiter_t i = kh_begin(gr->keys); i != kh_end(gr->keys); i++) {
-                LSUP_Key *cur = kh_key(gr->keys, i);
-                if (
-                    cur[0] != spok[0] ||
-                    cur[1] != spok[1] ||
-                    cur[2] != spok[2]
-                ) {
-                    callback_fn(gr, res, i, ctx);
-                    rc = LSUP_OK;
+            while (LSUP_htable_iter(
+                        gr->keys, &cur, &i_spok, NULL) == LSUP_OK) {
+                if (LIKELY(
+                    i_spok[2] != spok[2] ||
+                    i_spok[0] != spok[0] ||
+                    i_spok[1] != spok[1]
+                )) {
+                    rc = callback_fn(gr, res, &i_spok, ctx);
                 }
             }
 
             return rc;
         }
 
-    } else if (sk != NULL_KEY) {
-        k1 = sk;
+    } else if (spok[0] != NULL_KEY) {
+        k1 = spok[0];
 
-        if (pk != NULL_KEY) { // s p ?
-            k2 = pk;
+        if (spok[1] != NULL_KEY) { // s p ?
+            k2 = spok[1];
             cmp_fn = lookup_skpk_cmp_fn;
 
-        } else if (ok != NULL_KEY) { // s ? o
-            k2 = ok;
+        } else if (spok[2] != NULL_KEY) { // s ? o
+            k2 = spok[2];
             cmp_fn = lookup_skok_cmp_fn;
 
         } else { // s ? ?
@@ -366,19 +358,19 @@ int LSUP_graph_match_callback(
 
         }
 
-    } else if (pk != NULL_KEY) {
-        k1 = pk;
+    } else if (spok[1] != NULL_KEY) {
+        k1 = spok[1];
 
-        if (ok != NULL_KEY) { // ? p o
-            k2 = ok;
+        if (spok[2] != NULL_KEY) { // ? p o
+            k2 = spok[2];
             cmp_fn = lookup_pkok_cmp_fn;
 
         } else { // ? p ?
             cmp_fn = lookup_pk_cmp_fn;
         }
 
-    } else if (ok != NULL_KEY) { // ? ? o
-        k1 = ok;
+    } else if (spok[2] != NULL_KEY) { // ? ? o
+        k1 = spok[2];
         cmp_fn = lookup_ok_cmp_fn;
 
     } else {
@@ -386,10 +378,9 @@ int LSUP_graph_match_callback(
         return LSUP_graph_copy(res, gr);
     }
 
-    for(khiter_t i = kh_begin(gr->keys); i != kh_end(gr->keys); i++) {
-        LSUP_TripleKey *spok = (LSUP_TripleKey*)&kh_key(gr->keys, i);
-        if (cmp_fn(spok, k1, k2) == match_cond)
-            callback_fn(gr, res, i, ctx);
+    while (LSUP_htable_iter(gr->keys, &cur, &i_spok, NULL) == LSUP_OK) {
+        if (cmp_fn(&i_spok, k1, k2) == match_cond)
+            callback_fn(gr, res, &i_spok, ctx);
     }
 
     return LSUP_OK;
@@ -400,9 +391,7 @@ int LSUP_graph_lookup(LSUP_Graph *gr, LSUP_Graph *res, const LSUP_Triple *spo)
 {
     LSUP_graph_init(res, LOOKUP_GR_INIT_SIZE, NULL, LSUP_STORE_MEM);
 
-    LSUP_graph_match_callback(gr, res, spo, &match_add_fn, true, NULL);
-
-    return LSUP_OK;
+    return LSUP_graph_match_callback(gr, res, spo, &match_add_fn, true, NULL);
 }
 
 
@@ -416,19 +405,18 @@ int LSUP_graph_join(LSUP_Graph *gr1, LSUP_Graph *gr2, LSUP_Graph *res)
 
 int LSUP_graph_subtract(LSUP_Graph *gr1, LSUP_Graph *gr2, LSUP_Graph *res)
 {
-    if (kh_size(gr2->keys) == 0) return LSUP_graph_copy(gr1, res);
+    if (LSUP_htable_size(gr2->keys) == 0) return LSUP_graph_copy(gr1, res);
 
     LSUP_graph_init(res, LSUP_graph_capacity(gr1), NULL, LSUP_STORE_MEM);
 
-    if (kh_size(gr1->keys) == 0) return LSUP_OK;
+    if (LSUP_htable_size(gr1->keys) == 0) return LSUP_OK;
 
-    for(khiter_t i = kh_begin(gr1->keys); i != kh_end(gr1->keys); i++) {
-        LSUP_TripleKey *spok = (LSUP_TripleKey*)&kh_key(gr1->keys, i);
+    htsize_t cur = 0;
+    LSUP_TripleKey spok;
 
-        khiter_t cur = kh_get_Keys(gr2->keys, *spok);
-        if (cur == kh_end(gr2->keys)) {
-            match_add_fn(res, gr1, cur, NULL);
-        }
+    while(LSUP_htable_iter(gr1->keys, &cur, &spok, NULL) == LSUP_OK) {
+        if (LSUP_htable_get(gr2->keys, &spok, NULL) == LSUP_NORESULT)
+            match_add_fn(res, gr1, &spok, NULL);
     }
 
     return LSUP_OK;
@@ -439,16 +427,15 @@ int LSUP_graph_intersect(LSUP_Graph *gr1, LSUP_Graph *gr2, LSUP_Graph *res)
 {
     LSUP_graph_init(res, LSUP_graph_capacity(gr1), NULL, LSUP_STORE_MEM);
 
-    if (kh_size(gr1->keys) == 0 || kh_size(gr2->keys) == 0)
+    if (LSUP_htable_size(gr1->keys) == 0 || LSUP_htable_size(gr2->keys) == 0)
         return LSUP_OK;
 
-    for(khiter_t i = kh_begin(gr1->keys); i != kh_end(gr1->keys); i++) {
-        LSUP_TripleKey *spok = (LSUP_TripleKey*)&kh_key(gr1->keys, i);
+    htsize_t cur = 0;
+    LSUP_TripleKey spok;
 
-        khiter_t cur = kh_get_Keys(gr2->keys, *spok);
-        if (cur != kh_end(gr2->keys)) {
-            match_add_fn(res, gr1, cur, NULL);
-        }
+    while(LSUP_htable_iter(gr1->keys, &cur, &spok, NULL) == LSUP_OK) {
+        if (LSUP_htable_get(gr2->keys, &spok, NULL) == LSUP_OK)
+            match_add_fn(res, gr1, &spok, NULL);
     }
 
     return LSUP_OK;
@@ -457,29 +444,26 @@ int LSUP_graph_intersect(LSUP_Graph *gr1, LSUP_Graph *gr2, LSUP_Graph *res)
 
 int LSUP_graph_xor(LSUP_Graph *gr1, LSUP_Graph *gr2, LSUP_Graph *res)
 {
-    if (kh_size(gr1->keys) == 0) return LSUP_graph_copy(gr2, res);
-    if (kh_size(gr2->keys) == 0) return LSUP_graph_copy(gr1, res);
+    if (LSUP_htable_size(gr1->keys) == 0) return LSUP_graph_copy(gr2, res);
+    if (LSUP_htable_size(gr2->keys) == 0) return LSUP_graph_copy(gr1, res);
 
     LSUP_graph_init(
             res, min(LSUP_graph_capacity(gr1), LSUP_graph_capacity(gr2)),
             NULL, LSUP_STORE_MEM);
 
-    for(khiter_t i = kh_begin(gr1->keys); i != kh_end(gr1->keys); i++) {
-        LSUP_TripleKey *spok = (LSUP_TripleKey*)&kh_key(gr1->keys, i);
+    htsize_t cur = 0;
+    LSUP_TripleKey spok;
 
-        khiter_t cur = kh_get_Keys(gr2->keys, *spok);
-        if (cur == kh_end(gr2->keys)) {
-            match_add_fn(res, gr1, cur, NULL);
-        }
+    while(LSUP_htable_iter(gr1->keys, &cur, &spok, NULL) == LSUP_OK) {
+        if (LSUP_htable_get(gr2->keys, &spok, NULL) == LSUP_NORESULT)
+            match_add_fn(res, gr1, &spok, NULL);
     }
 
-    for(khiter_t i = kh_begin(gr2->keys); i != kh_end(gr2->keys); i++) {
-        LSUP_TripleKey *spok = (LSUP_TripleKey*)&kh_key(gr2->keys, i);
+    cur = 0;
 
-        khiter_t cur = kh_get_Keys(gr1->keys, *spok);
-        if (cur == kh_end(gr1->keys)) {
-            match_add_fn(res, gr2, cur, NULL);
-        }
+    while(LSUP_htable_iter(gr2->keys, &cur, &spok, NULL) == LSUP_OK) {
+        if (LSUP_htable_get(gr1->keys, &spok, NULL) == LSUP_NORESULT)
+            match_add_fn(res, gr2, &spok, NULL);
     }
 
     return LSUP_OK;
@@ -493,20 +477,16 @@ LSUP_graph_free(LSUP_Graph *gr)
         LSUP_term_free(gr->uri);
 
         // Free up triples.
-        /*
-        for(khiter_t i = kh_begin(gr->keys); i != kh_end(gr->keys); ++i) {
-            if(kh_exist(gr->keys, i))
-                free(&kh_value(gr->keys, i));
-        }
-        */
-        kh_destroy_Keys(gr->keys);
+        LSUP_htable_free(gr->keys);
 
         // Free up index entries and index.
-        for(khiter_t i = kh_begin(gr->idx); i != kh_end(gr->idx); ++i) {
-            if(kh_exist(gr->idx, i))
-                LSUP_buffer_done(&kh_value(gr->idx, i));
-        }
-        kh_destroy_Index(gr->idx);
+        htsize_t cur = 0;
+        LSUP_TripleKey spok;
+        void *sterm;
+        while(LSUP_htable_iter(gr->idx, &cur, &spok, &sterm) == LSUP_OK)
+            LSUP_buffer_done(sterm);
+
+        LSUP_htable_free(gr->idx);
 
         free(gr);
     }
@@ -522,35 +502,32 @@ LSUP_graph_free(LSUP_Graph *gr)
  *
  * The source graph cursor must be set to the triple to be copied.
  */
-int match_add_fn(LSUP_Graph *src, LSUP_Graph *dest, khiter_t cur, void *ctx)
+int match_add_fn(
+        LSUP_Graph *src, LSUP_Graph *dest, const LSUP_TripleKey *spok,
+        void *ctx)
 {
-    LSUP_TripleKey *spok = (LSUP_TripleKey*)&kh_key(src->keys, cur);
     // Add term to index.
     int status;
     for (int i = 0; i < 3; i++) {
-        khiter_t dest_cur = kh_put_Index(dest->idx, *spok[i], &status);
-        if (
-                kh_exist(src->idx, *spok[i] &&
-                (status == HS_EMPTY || status == HS_TOMBSTONE))) {
-            // If term is not indexed, store the struct in the index.
-            LSUP_buffer_copy(
-                    &kh_value(dest->idx, dest_cur), &kh_value(src->idx, cur));
+        // Index terms if not yet presents in destination.
+        void *src_val, *dest_val;
+
+        if(LSUP_htable_get(src->idx, *spok + i, &src_val) == LSUP_OK) {
+            CRITICAL(dest_val = malloc(sizeof(LSUP_Buffer)));
+            LSUP_buffer_copy(dest_val, src_val);
+            LSUP_htable_put(dest->idx, *spok + i, dest_val);
         }
     }
 
     // Add triple.
-    kh_put_Keys(src->keys, *spok, &status);
-
-    return LSUP_OK;
+    return LSUP_htable_put(dest->keys, spok, NULL);
 }
 
 
 /**
  * Callback for removing a matched triple.
  */
-int match_rm_fn(LSUP_Graph *src, LSUP_Graph *dest, khint_t cur, void *ctx)
-{
-    kh_del_Keys(dest->keys, cur);
-
-    return LSUP_OK;
-}
+int match_rm_fn(
+        LSUP_Graph *src, LSUP_Graph *dest, const LSUP_TripleKey *spok,
+        void *ctx)
+{ return LSUP_htable_del(dest->keys, spok); }

+ 423 - 431
src/htable.c

@@ -1,498 +1,490 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+
 #include "include/htable.h"
 
+
 #define BUCKET_EMPTY        1 << 0
 #define BUCKET_DELETED      1 << 1
 
-#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
+#if defined(DEBUG)
+#define    ASSERT        assert
+#else
+#define    ASSERT(x)
+#endif
 
-static const double __ac_HASH_UPPER = 0.77;
+#define    MAX_GROWTH_STEP          (1024U * 1024)
+
+#define    APPROX_85_PERCENT(x)     (((x) * 870) >> 10)
+#define    APPROX_40_PERCENT(x)     (((x) * 409) >> 10)
+
+
+typedef struct {
+    void *          key;
+    void *          val;
+    uint64_t        hash        : 32;
+    uint64_t        psl         : 16;
+} bucket_t;
+
+typedef struct htable_t {
+    unsigned        size;
+    unsigned        nitems;
+    unsigned        flags;
+    uint64_t        divinfo;
+    bucket_t *   buckets;
+    uint64_t        hashkey;
+
+    key_hash_fn_t   key_hash_fn;
+    key_eq_fn_t     key_eq_fn;
+
+    void *          key_data;
+    ksize_t         ksize;
+    void *          val_data;
+    vsize_t         vsize;
+
+    /*
+     * Small optimisation for a single element case: allocate one
+     * bucket together with the hashmap structure -- it will generally
+     * fit within the same cache-line.
+     */
+    bucket_t    init_bucket;
+} HTable;
 
-typedef uint8_t flags_t;
 
-typedef struct HTable {
-        ht_size_t n_buckets;            // # of buckets. Up to UINT_MAX
-        ht_size_t size;                 // # of entries in the table.
-        ht_size_t n_occupied;           // # of occupied buckets.
-        ht_size_t upper_bound;          // 
 
-        flags_t *flags;                 // Flags for each bucket.
+/* * * GENERIC UTILITIES * * */
 
-        key_hash_fn_t key_hash_fn;      // Function to compute hash of a key.
-        key_eq_fn_t key_eq_fn;          // Function to evaluate equality of keys.
+/*
+ * Find first bit.
+ */
+static inline int fls(int x)
+{ return x ? (sizeof(int) * CHAR_BIT) - __builtin_clz(x) : 0; }
 
-        uint32_t ksize;                 // Key size, in bytes.
-        uint32_t vsize;                 // Value size, in bytes.
 
-        void *keys;                     // Key data. This is a void pointer to
-                                        // a memory block that is looked up in
-                                        // "steps" determined by the ksize
-                                        // value.
-        void *vals;                     // Value data, same layout as keys.
-} HTable;
+/*
+ * Fast 32bit division and remainder.
+ *
+ * Reference:
+ *
+ *    Torbjörn Granlund and Peter L. Montgomery, "Division by Invariant
+ *    Integers Using Multiplication", ACM SIGPLAN Notices, Issue 6, Vol 29,
+ *    http://gmplib.org/~tege/divcnst-pldi94.pdf, 61-72, June 1994.
+ *
+ * The following example computes q = a / b and r = a % b:
+ *
+ *    uint64_t divinfo = fast_div32_init(b);
+ *    q = fast_div32(a, b, divinfo);
+ *    r = fast_rem32(a, b, divinfo);
+ */
+
+static inline uint64_t
+fast_div32_init(uint32_t div)
+{
+    uint64_t mt;
+    uint8_t s1, s2;
+    int l;
+
+    l = fls(div - 1);
+    mt = (uint64_t)(0x100000000ULL * ((1ULL << l) - div));
+    s1 = (l > 1) ? 1U : (uint8_t)l;
+    s2 = (l == 0) ? 0 : (uint8_t)(l - 1);
+    return (uint64_t)(mt / div + 1) << 32 | (uint32_t)s1 << 8 | s2;
+}
+
+static inline uint32_t
+fast_div32(uint32_t v, uint32_t div, uint64_t divinfo)
+{
+    const uint32_t m = divinfo >> 32;
+    const unsigned s1 = (divinfo & 0x0000ff00) >> 8;
+    const unsigned s2 = (divinfo & 0x000000ff);
+    const uint32_t t = (uint32_t)(((uint64_t)v * m) >> 32);
+    (void)div; // unused
+    return (t + ((v - t) >> s1)) >> s2;
+}
+
 
+static inline uint32_t
+fast_rem32(uint32_t v, uint32_t div, uint64_t divinfo)
+{ return v - div * fast_div32(v, div, divinfo); }
 
-/* * * Static prototypes * * */
 
-static int LSUP_htable_resize(HTable *ht, ht_size_t new_n_buckets);
+//static int __attribute__((__unused__))
+static int
+validate_psl_p(HTable *ht, const bucket_t *bucket, unsigned i)
+{
+    unsigned base_i = fast_rem32(bucket->hash, ht->size, ht->divinfo);
+    unsigned diff = (base_i > i) ? ht->size - base_i + i : i - base_i;
+    return bucket->key == NULL || diff == bucket->psl;
+}
 
 
-/* * * API * * */
+/* * * PUBLIC API * * */
 
+/*
+ * Construct a new hash table.
+ *
+ * => If size is non-zero, then pre-allocate the given number of buckets;
+ * => If size is zero, then a default minimum is used.
+ */
 int LSUP_htable_init(
-        HTable *ht, ht_size_t size, uint32_t ksize, uint32_t vsize,
-        key_hash_fn_t key_hash_fn, key_eq_fn_t key_eq_fn)
+        HTable *ht, htsize_t size, ksize_t ksize, vsize_t vsize,
+        key_hash_fn_t key_hash_fn, key_eq_fn_t key_eq_fn, unsigned flags)
 {
-    ht->key_hash_fn = key_hash_fn;
-    ht->key_eq_fn = key_eq_fn;
-
-    ht->ksize = ksize;
-    ht->vsize = vsize;
+    ht->flags = flags;
+    if (LSUP_htable_resize(ht, size) != 0) {
+        free(ht);
+        return -1;
+    }
+    ASSERT(ht->buckets);
+    ASSERT(ht->size);
 
-    return LSUP_htable_resize(ht, size);
+    return LSUP_OK;
 }
 
 
-void LSUP_htable_done(HTable *ht)
+HTable *LSUP_htable_new(
+        htsize_t size, ksize_t ksize, vsize_t vsize,
+        key_hash_fn_t key_hash_fn, key_eq_fn_t key_eq_fn, unsigned flags)
 {
-    if (LIKELY(ht != NULL)) {
-        free((void *)ht->keys);
-        free(ht->flags);
-        free((void *)ht->vals);
-    }
+    HTable *ht;
+    CRITICAL(ht = malloc(sizeof(HTable)));
+
+    LSUP_htable_init(ht, size, ksize, vsize, key_hash_fn, key_eq_fn, flags);
+
+    return ht;
 }
 
 
-ht_size_t LSUP_htable_get(const HTable *ht, void *key)
+/**
+ * Resize a table.
+ */
+int LSUP_htable_resize(HTable *ht, htsize_t newsize)
 {
-    if (ht->n_buckets > 0) {
-        ht_size_t i, last, mask, step = 0;
+    bucket_t *oldbuckets = ht->buckets;
+    const size_t oldsize = ht->size;
+    bucket_t *newbuckets;
+
+    ASSERT(newsize > 0);
+    ASSERT(newsize > ht->nitems);
+
+    /*
+     * Check for an overflow and allocate buckets.  Also, generate
+     * a new hash key/seed every time we resize the hash table.
+     */
+    if (newsize == 1) {
+        memset(&ht->init_bucket, 0, sizeof(bucket_t));
+        newbuckets = &ht->init_bucket;
+    } else if (newsize > UINT_MAX) {
+        return -1;
+    }
 
-        mask = ht->n_buckets - 1;
-        i = ht->key_hash_fn(key) & mask;
+    CRITICAL(ht->buckets = calloc(1, newsize * sizeof(bucket_t)));
+    CRITICAL(ht->key_data = realloc(ht->key_data, newsize * ht->ksize));
+    CRITICAL(ht->val_data = realloc(ht->key_data, newsize * ht->vsize));
 
-        last = i;
+    ht->size = newsize;
+    ht->nitems = 0;
 
-        while (
-                !CHK_FLAG(ht->flags[i], BUCKET_EMPTY) &&
-                (
-                    CHK_FLAG(ht->flags[i], BUCKET_DELETED) ||
-                    !ht->key_eq_fn(ht->keys + i * ht->ksize, key)
-                )) {
-            i = (i + (++step)) & mask;
+    ht->divinfo = fast_div32_init(newsize);
+    ht->hashkey ^= random() | (random() << 32);
 
-            if (i == last) return ht->n_buckets;
-        }
-        return CHK_FLAG(ht->flags[i], (BUCKET_EMPTY | BUCKET_DELETED)) ?
-            ht->n_buckets : i;
+    for (unsigned i = 0; i < oldsize; i++) {
+        const bucket_t *bucket = &oldbuckets[i];
 
-    } else return 0;
+        /* Skip the empty buckets. */
+        if (!bucket->key) {
+            continue;
+        }
+        LSUP_htable_insert(ht, bucket->key, bucket->val);
+        if ((ht->flags & HTABLE_NOCOPY) == 0) {
+            free(bucket->key);
+        }
+    }
+    if (oldbuckets && oldbuckets != &ht->init_bucket) {
+        free(oldbuckets);
+    }
+    return 0;
 }
 
 
+htsize_t LSUP_htable_capacity(LSUP_HTable *ht)
+{ return ht->size; }
 
 
+htsize_t LSUP_htable_size(LSUP_HTable *ht)
+{ return ht->nitems; }
 
-#ifndef kroundup32
-#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
-#endif
 
+/*
+ * Insert without resizing (assuming resizing is already done).
+ */
+int LSUP_htable_insert(HTable *ht, const void *key, void *val)
+{
+    const uint32_t hash = ht->key_hash_fn(key, ht->ksize, ht->hashkey);
+    bucket_t *bucket, entry;
+    unsigned i;
 
-int LSUP_htable_resize(HTable *ht, ht_size_t new_n_buckets)
-{ /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */
-    flags_t *new_flags = 0;
-    ht_size_t j = 1;
+    ASSERT(key != NULL);
 
-    kroundup32(new_n_buckets); // TODO make universal (not only 32-bit)
+    /*
+     * Setup the bucket entry.
+     */
+    if (ht->flags & HTABLE_NOCOPY) {
+        entry.key = (void *)(uintptr_t)key;
+    } else {
+        CRITICAL(entry.key = malloc(ht->ksize))
+        memcpy(entry.key, key, ht->ksize);
+    }
+    entry.hash = hash;
+    entry.val = val;
+    entry.psl = 0;
+
+    /*
+     * From the paper: "when inserting, if a record probes a location
+     * that is already occupied, the record that has traveled longer
+     * in its probe sequence keeps the location, and the other one
+     * continues on its probe sequence" (page 12).
+     *
+     * Basically: if the probe sequence length (PSL) of the element
+     * being inserted is greater than PSL of the element in the bucket,
+     * then swap them and continue.
+     */
+    i = fast_rem32(hash, ht->size, ht->divinfo);
+
+    while(1) {
+        bucket = &ht->buckets[i];
+        if (bucket->key) {
+            ASSERT(validate_psl_p(ht, bucket, i));
+
+            // There is a key in the bucket.
+            if (bucket->hash == hash && ht->key_eq_fn(
+                        bucket->key, key, ht->ksize)) {
+                // Duplicate key: do nothing.
+                if ((ht->flags & HTABLE_NOCOPY) == 0) {
+                    free(entry.key);
+                }
+                return LSUP_NOACTION;
+            }
 
-    if (new_n_buckets < 4) new_n_buckets = 4;
+            /*
+             * We found a "rich" bucket.  Capture its location.
+             */
+            if (entry.psl > bucket->psl) {
+                bucket_t tmp;
+
+                /*
+                 * Place our key-value pair by swapping the "rich"
+                 * bucket with our entry.  Copy the structures.
+                 */
+                tmp = entry;
+                entry = *bucket;
+                *bucket = tmp;
+            }
+            entry.psl++;
 
-    if (ht->size >= (ht_size_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) {
-        // requested size is too small
-        j = 0;
+            /* Continue to the next bucket. */
+            ASSERT(validate_psl_p(ht, bucket, i));
+            i = fast_rem32(i + 1, ht->size, ht->divinfo);
+        }
+    }
 
-    } else {
-        // hash table size to be changed (shrink or expand); rehash
-        CRITICAL(new_flags = (flags_t*)malloc(new_n_buckets * sizeof(flags_t)));
+    /*
+     * Found a free bucket: insert the entry.
+     */
+    *bucket = entry; // copy
+    ht->nitems++;
 
-        memset(new_flags, 0, new_n_buckets * sizeof(flags_t));
+    ASSERT(validate_psl_p(ht, bucket, i));
 
-        if (ht->n_buckets < new_n_buckets) {
-            // Expand.
-            CRITICAL(ht->keys = realloc(ht->keys, new_n_buckets * ht->ksize));
+    return LSUP_OK;
+}
 
-            if (ht->vsize > 0) {
-                // Not for hash sets.
-                CRITICAL(ht->vals = realloc(
-                        ht->vals, new_n_buckets * ht->vsize));
-            }
-        }
+
+/*
+ * rhashmap_put: insert a value given the key.
+ *
+ * => If the key is already present, return its associated value.
+ * => Otherwise, on successful insert, return the given value.
+ */
+int LSUP_htable_put(HTable *ht, const void *key, void *val)
+{
+    const size_t threshold = APPROX_85_PERCENT(ht->size);
+
+    /*
+     * If the load factor is more than the threshold, then resize.
+     */
+    if (UNLIKELY(ht->nitems > threshold)) {
+        /*
+         * Grow the hash table by doubling its size, but with
+         * a limit of MAX_GROWTH_STEP.
+         */
+        const size_t grow_limit = ht->size + MAX_GROWTH_STEP;
+        const size_t newsize = min(ht->size << 1, grow_limit);
+        LSUP_htable_resize(ht, newsize);
     }
 
-    if (j) { /* rehashing is needed */
-        for (j = 0; j != ht->n_buckets; ++j) {
-            if (__ac_iseither(ht->flags, j) == 0) {
-                khkey_t key = ht->keys[j];
-                khval_t val;
-                khint_t new_mask;
-                new_mask = new_n_buckets - 1;
-                if (ht->vsize > 0) val = ht->vals[j];
-                __ac_set_isdel_true(ht->flags, j);
-                while (1) { /* kick-out process; sort of like in Cuckoo hashing */
-                    khint_t k, i, step = 0;
-                    k = __hash_func(key);
-                    i = k & new_mask;
-                    while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask;
-                    __ac_set_isempty_false(new_flags, i);
-                    if (i < ht->n_buckets && __ac_iseither(ht->flags, i) == 0) { /* kick out the existing element */
-                        { khkey_t tmp = ht->keys[i]; ht->keys[i] = key; key = tmp; }
-                        if (ht->vsize > 0) { khval_t tmp = ht->vals[i]; ht->vals[i] = val; val = tmp; }
-                        __ac_set_isdel_true(ht->flags, i); /* mark it as deleted in the old hash table */
-                    } else { /* write the element and jump out of the loop */
-                        ht->keys[i] = key;
-                        if (ht->vsize > 0) ht->vals[i] = val;
-                        break;
-                    }
-                }
-            }
-        }
-        if (ht->n_buckets > new_n_buckets) { /* shrink the hash table */
-            ht->keys = (khkey_t*)realloc((void *)ht->keys, new_n_buckets * sizeof(khkey_t));
-            if (ht->vsize > 0) ht->vals = (khval_t*)realloc((void *)ht->vals, new_n_buckets * sizeof(khval_t));
+    return LSUP_htable_insert(ht, key, val);
+}
+
+
+int LSUP_htable_get(const HTable *ht, const void *key, void **valp)
+{
+    const uint64_t hash = ht->key_hash_fn(key, ht->ksize, ht->hashkey);
+    htsize_t n = 0, i = fast_rem32(hash, ht->size, ht->divinfo);
+
+    if (key == NULL) return LSUP_VALUE_ERR;
+
+    /*
+     * Lookup is a linear probe.
+     */
+    while (1) {
+        bucket_t *bucket = &ht->buckets[i];
+        ASSERT(validate_psl_p(ht, bucket, i));
+
+        if (bucket->hash == hash && ht->key_eq_fn(
+                    bucket->key, key, ht->ksize))
+            if (valp != NULL)
+                *valp = ht->vsize == 0 ? NULL : bucket->val;
+
+            return LSUP_OK;
+
+        /*
+         * Stop probing if we hit an empty bucket; also, if we hit a
+         * bucket with PSL lower than the distance from the base location,
+         * then it means that we found the "rich" bucket which should
+         * have been captured, if the key was inserted -- see the central
+         * point of the algorithm in the insertion function.
+         */
+        if (!bucket->key || n > bucket->psl) {
+            valp = NULL;
+            return LSUP_NORESULT;
         }
-        kfree(ht->flags); /* free the working space */
-        ht->flags = new_flags;
-        ht->n_buckets = new_n_buckets;
-        ht->n_occupied = ht->size;
-        ht->upper_bound = (khint_t)(ht->n_buckets * __ac_HASH_UPPER + 0.5);
+        n++;
+
+        /* Continue to the next bucket. */
+        i = fast_rem32(i + 1, ht->size, ht->divinfo);
     }
-    return 0;
 }
-SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret)
+
+
+int LSUP_htable_del(HTable *ht, const void *key)
 {
-    khint_t x;
-    if (h->n_occupied >= h->upper_bound) { /* update the hash table */
-        if (h->n_buckets > (h->size<<1)) {
-            if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */
-                *ret = -1; return h->n_buckets;
-            }
-        } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */
-            *ret = -1; return h->n_buckets;
+    const size_t threshold = APPROX_40_PERCENT(ht->size);
+    const uint32_t hash = ht->key_hash_fn(key, ht->ksize, ht->hashkey);
+    unsigned n = 0, i = fast_rem32(hash, ht->size, ht->divinfo);
+    bucket_t *bucket;
+
+    ASSERT(key != NULL);
+
+    while(1) {
+        /*
+         * The same probing logic as in the lookup function.
+         */
+        bucket = &ht->buckets[i];
+        if (!bucket->key || n > bucket->psl) {
+            return LSUP_NOACTION;
         }
-        printf("capacity now %d\n\n", h->n_buckets);
-    } /* TODO: to implement automatically shrinking; resize() already support shrinking */
-    {
-        khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0;
-        x = site = h->n_buckets; k = __hash_func(key); i = k & mask;
-        if (__ac_isempty(h->flags, i)) x = i; /* for speed up */
-        else {
-            last = i;
-            printf("Duplicate\n");
-            printf("Keys[i]: %lx\n", (size_t)h->keys[i]);
-            printf("Key: %lx\n", (size_t)key);
-            while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) {
-                if (__ac_isdel(h->flags, i)) site = i;
-                i = (i + (++step)) & mask;
-                if (i == last) { x = site; break; }
-            }
-            if (x == h->n_buckets) {
-                if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site;
-                else x = i;
-            }
+        ASSERT(validate_psl_p(ht, bucket, i));
+
+        if (
+                bucket->hash != hash ||
+                ht->key_eq_fn(bucket->key, key, ht->ksize)) {
+            /* Continue to the next bucket. */
+            i = fast_rem32(i + 1, ht->size, ht->divinfo);
+            n++;
         }
     }
-    if (__ac_isempty(h->flags, x)) { /* not present at all */
-        h->keys[x] = key;
-        __ac_set_isboth_false(h->flags, x);
-        ++h->size; ++h->n_occupied;
-        *ret = 1;
-    } else if (__ac_isdel(h->flags, x)) { /* deleted */
-        h->keys[x] = key;
-        __ac_set_isboth_false(h->flags, x);
-        ++h->size;
-        *ret = 2;
-    } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */
-    return x;
+
+    /*
+     * Free the bucket.
+     */
+    if ((ht->flags & HTABLE_NOCOPY) == 0) {
+        free(bucket->key);
+    }
+    ht->nitems--;
+
+    /*
+     * The probe sequence must be preserved in the deletion case.
+     * Use the backwards-shifting method to maintain low variance.
+     */
+    while(1) {
+        bucket_t *nbucket;
+
+        bucket->key = NULL;
+
+        i = fast_rem32(i + 1, ht->size, ht->divinfo);
+        nbucket = &ht->buckets[i];
+        ASSERT(validate_psl_p(ht, nbucket, i));
+
+        /*
+         * Stop if we reach an empty bucket or hit a key which
+         * is in its base (original) location.
+         */
+        if (!nbucket->key || nbucket->psl == 0) {
+            break;
+        }
+
+        nbucket->psl--;
+        *bucket = *nbucket;
+        bucket = nbucket;
+    }
+
+    /*
+     * If the load factor is less than threshold, then shrink by
+     * halving the size, but not less than 1.
+     */
+    if (ht->nitems < threshold) {
+        size_t newsize = max(ht->size >> 1, 1);
+        (void)LSUP_htable_resize(ht, newsize);
+    }
+
+    return LSUP_OK;
 }
-SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x)
+
+
+extern int LSUP_htable_iter(
+        LSUP_HTable *ht, htsize_t *cur, void *key, void **valp)
 {
-    if (x != h->n_buckets && !__ac_iseither(h->flags, x)) {
-        __ac_set_isdel_true(h->flags, x);
-        --h->size;
+    while (*cur < ht->size) {
+        bucket_t *bucket = &ht->buckets[*cur];
+
+        *cur++;
+
+        if (!bucket->key) continue;
+
+        memcpy(key, bucket->key, ht->ksize);
+        if (valp != NULL && ht->vsize > 0) *valp = bucket->val;
+
+        return LSUP_OK;
     }
+    return LSUP_END;
 }
 
-/*
-            printf("Not empty: %d\n", !__ac_isempty(h->flags, i));
-            printf("deleted: %d\n", __ac_isdel(h->flags, i));
-            printf("Hash does not match: %d\n\n", !__hash_equal(h->keys[i], key));
-*/
-#define KHASH_DECLARE(name, khkey_t, khval_t)
-__KHASH_TYPE(name, khkey_t, khval_t)
-__KHASH_PROTOTYPES(name, khkey_t, khval_t)
-
-#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
-__KHASH_TYPE(name, khkey_t, khval_t)
-__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
-
-#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
-KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
-
-/* --- BEGIN OF HASH FUNCTIONS --- */
-
-/*! @function
-@abstract     Integer hash function
-@param  key   The integer [ht_size_t]
-@return       The hash value [khint_t]
-*/
-#define kh_int_hash_func(key) (ht_size_t)(key)
-/*! @function
-@abstract     Integer comparison function
-*/
-#define kh_int_hash_equal(a, b) ((a) == (b))
-/*! @function
-@abstract     64-bit integer hash function
-@param  key   The integer [khint64_t]
-@return       The hash value [khint_t]
-*/
-#define kh_int64_hash_func(key) (ht_size_t)((key)>>33^(key)^(key)<<11)
-/*! @function
-@abstract     64-bit integer comparison function
-*/
-#define kh_int64_hash_equal(a, b) ((a) == (b))
-/*! @function
-@abstract     const char* hash function
-@param  s     Pointer to a null terminated string
-@return       The hash value
-*/
-static kh_inline khint_t __ac_X31_hash_string(const char *s)
+
+void LSUP_htable_done(HTable *ht)
 {
-khint_t h = (khint_t)*s;
-if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
-return h;
+    if ((ht->flags & HTABLE_NOCOPY) == 0) {
+        for (htsize_t i = 0; i < ht->size; i++) {
+            const bucket_t *bucket = &ht->buckets[i];
+
+            if (bucket->key) {
+                free(bucket->key);
+            }
+        }
+    }
+    if (ht->buckets != &ht->init_bucket) {
+        free(ht->buckets);
+    }
 }
-/*! @function
-@abstract     Another interface to const char* hash function
-@param  key   Pointer to a null terminated string [const char*]
-@return       The hash value [khint_t]
-*/
-#define kh_str_hash_func(key) __ac_X31_hash_string(key)
-/*! @function
-@abstract     Const char* comparison function
-*/
-#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
-
-static kh_inline khint_t __ac_Wang_hash(khint_t key)
+
+
+void LSUP_htable_free(HTable *ht)
 {
-key += ~(key << 15);
-key ^=  (key >> 10);
-key +=  (key << 3);
-key ^=  (key >> 6);
-key += ~(key << 11);
-key ^=  (key >> 16);
-return key;
+    if(LIKELY(ht != NULL)) {
+        LSUP_htable_done(ht);
+        free(ht);
+    }
 }
-#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key)
-
-/* --- END OF HASH FUNCTIONS --- */
-
-/* Other convenient macros... */
-
-/*!
-@abstract Type of the hash table.
-@param  name  Name of the hash table [symbol]
-*/
-#define khash_t(name) kh_##name##_t
-
-/*! @function
-@abstract     Initiate a hash table.
-@param  name  Name of the hash table [symbol]
-@return       Pointer to the hash table [khash_t(name)*]
-*/
-#define kh_init(name) kh_init_##name()
-
-/*! @function
-@abstract     Destroy a hash table.
-@param  name  Name of the hash table [symbol]
-@param  h     Pointer to the hash table [khash_t(name)*]
-*/
-#define kh_destroy(name, h) kh_destroy_##name(h)
-
-/*! @function
-@abstract     Reset a hash table without deallocating memory.
-@param  name  Name of the hash table [symbol]
-@param  h     Pointer to the hash table [khash_t(name)*]
-*/
-#define kh_clear(name, h) kh_clear_##name(h)
-
-/*! @function
-@abstract     Resize a hash table.
-@param  name  Name of the hash table [symbol]
-@param  h     Pointer to the hash table [khash_t(name)*]
-@param  s     New size [khint_t]
-*/
-#define kh_resize(name, h, s) kh_resize_##name(h, s)
-
-/*! @function
-@abstract     Insert a key to the hash table.
-@param  name  Name of the hash table [symbol]
-@param  h     Pointer to the hash table [khash_t(name)*]
-@param  k     Key [type of keys]
-@param  r     Extra return code: -1 if the operation failed;
-            0 if the key is present in the hash table;
-            1 if the bucket is empty (never used); 2 if the element in
-            the bucket has been deleted [int*]
-@return       Iterator to the inserted element [khint_t]
-*/
-#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
-
-/*! @function
-@abstract     Retrieve a key from the hash table.
-@param  name  Name of the hash table [symbol]
-@param  h     Pointer to the hash table [khash_t(name)*]
-@param  k     Key [type of keys]
-@return       Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
-*/
-#define kh_get(name, h, k) kh_get_##name(h, k)
-
-/*! @function
-@abstract     Remove a key from the hash table.
-@param  name  Name of the hash table [symbol]
-@param  h     Pointer to the hash table [khash_t(name)*]
-@param  k     Iterator to the element to be deleted [khint_t]
-*/
-#define kh_del(name, h, k) kh_del_##name(h, k)
-
-/*! @function
-@abstract     Test whether a bucket contains data.
-@param  h     Pointer to the hash table [khash_t(name)*]
-@param  x     Iterator to the bucket [khint_t]
-@return       1 if containing data; 0 otherwise [int]
-*/
-#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
-
-/*! @function
-@abstract     Get key given an iterator
-@param  h     Pointer to the hash table [khash_t(name)*]
-@param  x     Iterator to the bucket [khint_t]
-@return       Key [type of keys]
-*/
-#define kh_key(h, x) ((h)->keys[x])
-
-/*! @function
-@abstract     Get value given an iterator
-@param  h     Pointer to the hash table [khash_t(name)*]
-@param  x     Iterator to the bucket [khint_t]
-@return       Value [type of values]
-@discussion   For hash sets, calling this results in segfault.
-*/
-#define kh_val(h, x) ((h)->vals[x])
-
-/*! @function
-@abstract     Alias of kh_val()
-*/
-#define kh_value(h, x) ((h)->vals[x])
-
-/*! @function
-@abstract     Get the start iterator
-@param  h     Pointer to the hash table [khash_t(name)*]
-@return       The start iterator [khint_t]
-*/
-#define kh_begin(h) (khint_t)(0)
-
-/*! @function
-@abstract     Get the end iterator
-@param  h     Pointer to the hash table [khash_t(name)*]
-@return       The end iterator [khint_t]
-*/
-#define kh_end(h) ((h)->n_buckets)
-
-/*! @function
-@abstract     Get the number of elements in the hash table
-@param  h     Pointer to the hash table [khash_t(name)*]
-@return       Number of elements in the hash table [khint_t]
-*/
-#define kh_size(h) ((h)->size)
-
-/*! @function
-@abstract     Get the number of buckets in the hash table
-@param  h     Pointer to the hash table [khash_t(name)*]
-@return       Number of buckets in the hash table [khint_t]
-*/
-#define kh_n_buckets(h) ((h)->n_buckets)
-
-/*! @function
-@abstract     Iterate over the entries in the hash table
-@param  h     Pointer to the hash table [khash_t(name)*]
-@param  kvar  Variable to which key will be assigned
-@param  vvar  Variable to which value will be assigned
-@param  code  Block of code to execute
-*/
-#define kh_foreach(h, kvar, vvar, code) { khint_t __i;
-for (__i = kh_begin(h); __i != kh_end(h); ++__i) {
-    if (!kh_exist(h,__i)) continue;
-    (kvar) = kh_key(h,__i);
-    (vvar) = kh_val(h,__i);
-    code;
-} }
-
-/*! @function
-@abstract     Iterate over the values in the hash table
-@param  h     Pointer to the hash table [khash_t(name)*]
-@param  vvar  Variable to which value will be assigned
-@param  code  Block of code to execute
-*/
-#define kh_foreach_value(h, vvar, code) { khint_t __i;
-for (__i = kh_begin(h); __i != kh_end(h); ++__i) {
-    if (!kh_exist(h,__i)) continue;
-    (vvar) = kh_val(h,__i);
-    code;
-} }
-
-/* More convenient interfaces */
-
-/*! @function
-@abstract     Instantiate a hash set containing integer keys
-@param  name  Name of the hash table [symbol]
-*/
-#define KHASH_SET_INIT_INT(name)
-KHASH_INIT(name, ht_size_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
-
-/*! @function
-@abstract     Instantiate a hash map containing integer keys
-@param  name  Name of the hash table [symbol]
-@param  khval_t  Type of values [type]
-*/
-#define KHASH_MAP_INIT_INT(name, khval_t)
-KHASH_INIT(name, ht_size_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
-
-/*! @function
-@abstract     Instantiate a hash set containing 64-bit integer keys
-@param  name  Name of the hash table [symbol]
-*/
-#define KHASH_SET_INIT_INT64(name)
-KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
-
-/*! @function
-@abstract     Instantiate a hash map containing 64-bit integer keys
-@param  name  Name of the hash table [symbol]
-@param  khval_t  Type of values [type]
-*/
-#define KHASH_MAP_INIT_INT64(name, khval_t)
-KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
-
-typedef const char *kh_cstr_t;
-/*! @function
-@abstract     Instantiate a hash map containing const char* keys
-@param  name  Name of the hash table [symbol]
-*/
-#define KHASH_SET_INIT_STR(name)
-KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
-
-/*! @function
-@abstract     Instantiate a hash map containing const char* keys
-@param  name  Name of the hash table [symbol]
-@param  khval_t  Type of values [type]
-*/
-#define KHASH_MAP_INIT_STR(name, khval_t)
-KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
-