hashtable.c

Go to the documentation of this file.
00001 
00085 #include "hashtable.h"
00086 #include <cfg/debug.h>
00087 #include <cfg/compiler.h>
00088 #include <cfg/macros.h> //ROTL(), ROTR();
00089 
00090 #include <string.h>
00091 
00092 
00093 typedef const void** HashNodePtr;
00094 #define NODE_EMPTY(node)               (!*(node))
00095 #define HT_HAS_INTERNAL_KEY(ht)        (CONFIG_HT_OPTIONAL_INTERNAL_KEY && ht->flags.key_internal)
00096 
00098 INLINE uint8_t *key_internal_get_ptr(struct HashTable *ht, HashNodePtr node)
00099 {
00100     uint8_t* key_buf = ht->key_data.mem;
00101     size_t index;
00102 
00103     // Compute the index of the node and use it to move within the whole key buffer
00104     index = node - &ht->mem[0];
00105     ASSERT(index < (size_t)(1 << ht->max_elts_log2));
00106     key_buf += index * (INTERNAL_KEY_MAX_LENGTH + 1);
00107 
00108     return key_buf;
00109 }
00110 
00111 
00112 INLINE void node_get_key(struct HashTable* ht, HashNodePtr node, const void** key, uint8_t* key_length)
00113 {
00114     if (HT_HAS_INTERNAL_KEY(ht))
00115     {
00116         uint8_t* k = key_internal_get_ptr(ht, node);
00117 
00118         // Key has its length stored in the first byte
00119         *key_length = *k++;
00120         *key = k;
00121     }
00122     else
00123         *key = ht->key_data.hook(*node, key_length);
00124 }
00125 
00126 
00127 INLINE bool node_key_match(struct HashTable* ht, HashNodePtr node, const void* key, uint8_t key_length)
00128 {
00129     const void* key2;
00130     uint8_t key2_length;
00131 
00132     node_get_key(ht, node, &key2, &key2_length);
00133 
00134     return (key_length == key2_length && memcmp(key, key2, key_length) == 0);
00135 }
00136 
00137 
00138 static uint16_t calc_hash(const void* _key, uint8_t key_length)
00139 {
00140     const char* key = (const char*)_key;
00141     uint16_t hash = key_length;
00142     int i;
00143     int len = (int)key_length;
00144 
00145     for (i = 0; i < len; ++i)
00146         hash = ROTL(hash, 4) ^ key[i];
00147 
00148     return hash ^ (hash >> 6) ^ (hash >> 13);
00149 }
00150 
00151 
00152 static HashNodePtr perform_lookup(struct HashTable* ht,
00153                                   const void* key, uint8_t key_length)
00154 {
00155     uint16_t hash = calc_hash(key, key_length);
00156     uint16_t mask = ((1 << ht->max_elts_log2) - 1);
00157     uint16_t index = hash & mask;
00158     uint16_t first_index = index;
00159     uint16_t step;
00160     HashNodePtr node;
00161 
00162     // Fast-path optimization: we check immediately if the current node
00163     //  is the one we were looking for, so we save the computation of the
00164     //  increment step in the common case.
00165     node = &ht->mem[index];
00166     if (NODE_EMPTY(node)
00167         || node_key_match(ht, node, key, key_length))
00168         return node;
00169 
00170     // Increment while going through the hash table in case of collision.
00171     //  This implements the double-hash technique: we use the higher part
00172     //  of the hash as a step increment instead of just going to the next
00173     //  element, to minimize the collisions.
00174     // Notice that the number must be odd to be sure that the whole table
00175     //  is traversed. Actually MCD(table_size, step) must be 1, but
00176     //  table_size is always a power of 2, so we just ensure that step is
00177     //  never a multiple of 2.
00178     step = (ROTR(hash, ht->max_elts_log2) & mask) | 1;
00179 
00180     do
00181     {
00182         index += step;
00183         index &= mask;
00184 
00185         node = &ht->mem[index];
00186         if (NODE_EMPTY(node)
00187             || node_key_match(ht, node, key, key_length))
00188             return node;
00189 
00190         // The check is done after the key compare. This actually causes
00191         //  one more compare in the case the table is full (since the first
00192         //  element was compared at the very start, and then at the end),
00193         //  but it makes faster the common path where we enter this loop
00194         //  for the first time, and index will not match first_index for
00195         //  sure.
00196     } while (index != first_index);
00197 
00198     return NULL;
00199 }
00200 
00201 
00202 void ht_init(struct HashTable* ht)
00203 {
00204     memset(ht->mem, 0, sizeof(ht->mem[0]) * (1 << ht->max_elts_log2));
00205 }
00206 
00207 
00208 static bool insert(struct HashTable* ht, const void* key, uint8_t key_length, const void* data)
00209 {
00210     HashNodePtr node;
00211 
00212     if (!data)
00213         return false;
00214 
00215     if (HT_HAS_INTERNAL_KEY(ht))
00216         key_length = MIN(key_length, (uint8_t)INTERNAL_KEY_MAX_LENGTH);
00217 
00218     node = perform_lookup(ht, key, key_length);
00219     if (!node)
00220         return false;
00221 
00222     if (HT_HAS_INTERNAL_KEY(ht))
00223     {
00224         uint8_t* k = key_internal_get_ptr(ht, node);
00225         *k++ = key_length;
00226         memcpy(k, key, key_length);
00227     }
00228 
00229     *node = data;
00230     return true;
00231 }
00232 
00233 
00234 bool ht_insert_with_key(struct HashTable* ht, const void* key, uint8_t key_length, const void* data)
00235 {
00236 #ifdef _DEBUG
00237     if (!HT_HAS_INTERNAL_KEY(ht))
00238     {
00239         // Construct a fake node and use it to match the key
00240         HashNodePtr node = &data;
00241         if (!node_key_match(ht, node, key, key_length))
00242         {
00243             ASSERT2(0, "parameter key is different from the external key");
00244             return false;
00245         }
00246     }
00247 #endif
00248 
00249     return insert(ht, key, key_length, data);
00250 }
00251 
00252 
00253 bool ht_insert(struct HashTable* ht, const void* data)
00254 {
00255     const void* key;
00256     uint8_t key_length;
00257 
00258 #ifdef _DEBUG
00259     if (HT_HAS_INTERNAL_KEY(ht))
00260     {
00261         ASSERT("parameter cannot be a hash table with internal keys - use ht_insert_with_key()"
00262                && 0);
00263         return false;
00264     }
00265 #endif
00266 
00267     key = ht->key_data.hook(data, &key_length);
00268 
00269     return insert(ht, key, key_length, data);
00270 }
00271 
00272 
00273 const void* ht_find(struct HashTable* ht, const void* key, uint8_t key_length)
00274 {
00275     HashNodePtr node;
00276 
00277     if (HT_HAS_INTERNAL_KEY(ht))
00278         key_length = MIN(key_length, (uint8_t)INTERNAL_KEY_MAX_LENGTH);
00279 
00280     node = perform_lookup(ht, key, key_length);
00281 
00282     if (!node || NODE_EMPTY(node))
00283         return NULL;
00284 
00285     return *node;
00286 }
00287 
00288 
00289 #if 0
00290 
00291 #include <stdlib.h>
00292 
00293 bool ht_test(void);
00294 
00295 static const void* test_get_key(const void* ptr, uint8_t* length)
00296 {
00297     const char* s = ptr;
00298     *length = strlen(s);
00299     return s;
00300 }
00301 
00302 #define NUM_ELEMENTS   256
00303 DECLARE_HASHTABLE_STATIC(test1, 256, test_get_key);
00304 DECLARE_HASHTABLE_INTERNALKEY_STATIC(test2, 256);
00305 
00306 static char data[NUM_ELEMENTS][10];
00307 static char keydomain[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
00308 
00309 static bool single_test(void)
00310 {
00311     int i;
00312 
00313     ht_init(&test1);
00314     ht_init(&test2);
00315 
00316     for (i=0;i<NUM_ELEMENTS;i++)
00317     {
00318         int k;
00319         int klen;
00320 
00321         do
00322         {
00323             klen = (rand() % 8) + 1;
00324             for (k=0;k<klen;k++)
00325                 data[i][k] = keydomain[rand() % (sizeof(keydomain)-1)];
00326             data[i][k]=0;
00327         } while (ht_find_str(&test1, data[i]) != NULL);
00328 
00329         ASSERT(ht_insert(&test1, data[i]));
00330         ASSERT(ht_insert_str(&test2, data[i], data[i]));
00331     }
00332 
00333     for (i=0;i<NUM_ELEMENTS;i++)
00334     {
00335         char *found1, *found2;
00336 
00337         found1 = (char*)ht_find_str(&test1, data[i]);
00338         if (strcmp(found1, data[i]) != 0)
00339         {
00340             ASSERT(strcmp(found1,data[i]) == 0);
00341             return false;
00342         }
00343 
00344         found2 = (char*)ht_find_str(&test2, data[i]);
00345         if (strcmp(found2, data[i]) != 0)
00346         {
00347             ASSERT(strcmp(found2,data[i]) == 0);
00348             return false;
00349         }
00350     }
00351 
00352     return true;
00353 }
00354 
00355 static uint16_t rand_seeds[] = { 1, 42, 666, 0xDEAD, 0xBEEF, 0x1337, 0xB00B };
00356 
00357 bool ht_test(void)
00358 {
00359     int i;
00360 
00361     for (i=0;i<countof(rand_seeds);++i)
00362     {
00363         srand(rand_seeds[i]);
00364         if (!single_test())
00365         {
00366             kprintf("ht_test failed\n");
00367             return false;
00368         }
00369     }
00370 
00371     kprintf("ht_test successful\n");
00372     return true;
00373 }
00374 
00375 #endif