@ -0,0 +1,843 @@ | |||||
/* | |||||
* Hash Table Data Type | |||||
* Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net> | |||||
* | |||||
* Free Software License: | |||||
* | |||||
* All rights are reserved by the author, with the following exceptions: | |||||
* Permission is granted to freely reproduce and distribute this software, | |||||
* possibly in exchange for a fee, provided that this copyright notice appears | |||||
* intact. Permission is also granted to adapt this software to produce | |||||
* derivative works, as long as the modified versions carry this copyright | |||||
* notice and additional notices stating that the work has been modified. | |||||
* This source code may be translated into executable form and incorporated | |||||
* into proprietary software; there is no requirement for such software to | |||||
* contain a copyright notice related to this source. | |||||
* | |||||
* $Id: hash.c,v 1.36.2.11 2000/11/13 01:36:45 kaz Exp $ | |||||
* $Name: kazlib_1_20 $ | |||||
*/ | |||||
#include <stdlib.h> | |||||
#include <stddef.h> | |||||
#include <assert.h> | |||||
#include <string.h> | |||||
#define HASH_IMPLEMENTATION | |||||
#include "hash.h" | |||||
#ifdef KAZLIB_RCSID | |||||
static const char rcsid[] = "$Id: hash.c,v 1.36.2.11 2000/11/13 01:36:45 kaz Exp $"; | |||||
#endif | |||||
#define INIT_BITS 6 | |||||
#define INIT_SIZE (1UL << (INIT_BITS)) /* must be power of two */ | |||||
#define INIT_MASK ((INIT_SIZE) - 1) | |||||
#define next hash_next | |||||
#define key hash_key | |||||
#define data hash_data | |||||
#define hkey hash_hkey | |||||
#define table hash_table | |||||
#define nchains hash_nchains | |||||
#define nodecount hash_nodecount | |||||
#define maxcount hash_maxcount | |||||
#define highmark hash_highmark | |||||
#define lowmark hash_lowmark | |||||
#define compare hash_compare | |||||
#define function hash_function | |||||
#define allocnode hash_allocnode | |||||
#define freenode hash_freenode | |||||
#define context hash_context | |||||
#define mask hash_mask | |||||
#define dynamic hash_dynamic | |||||
#define table hash_table | |||||
#define chain hash_chain | |||||
static hnode_t *kl_hnode_alloc(void *context); | |||||
static void kl_hnode_free(hnode_t *node, void *context); | |||||
static hash_val_t hash_fun_default(const void *key); | |||||
static int hash_comp_default(const void *key1, const void *key2); | |||||
int hash_val_t_bit; | |||||
/* | |||||
* Compute the number of bits in the hash_val_t type. We know that hash_val_t | |||||
* is an unsigned integral type. Thus the highest value it can hold is a | |||||
* Mersenne number (power of two, less one). We initialize a hash_val_t | |||||
* object with this value and then shift bits out one by one while counting. | |||||
* Notes: | |||||
* 1. HASH_VAL_T_MAX is a Mersenne number---one that is one less than a power | |||||
* of two. This means that its binary representation consists of all one | |||||
* bits, and hence ``val'' is initialized to all one bits. | |||||
* 2. While bits remain in val, we increment the bit count and shift it to the | |||||
* right, replacing the topmost bit by zero. | |||||
*/ | |||||
static void compute_bits(void) | |||||
{ | |||||
hash_val_t val = HASH_VAL_T_MAX; /* 1 */ | |||||
int bits = 0; | |||||
while (val) { /* 2 */ | |||||
bits++; | |||||
val >>= 1; | |||||
} | |||||
hash_val_t_bit = bits; | |||||
} | |||||
/* | |||||
* Verify whether the given argument is a power of two. | |||||
*/ | |||||
static int is_power_of_two(hash_val_t arg) | |||||
{ | |||||
if (arg == 0) | |||||
return 0; | |||||
while ((arg & 1) == 0) | |||||
arg >>= 1; | |||||
return (arg == 1); | |||||
} | |||||
/* | |||||
* Compute a shift amount from a given table size | |||||
*/ | |||||
static hash_val_t compute_mask(hashcount_t size) | |||||
{ | |||||
assert (is_power_of_two(size)); | |||||
assert (size >= 2); | |||||
return size - 1; | |||||
} | |||||
/* | |||||
* Initialize the table of pointers to null. | |||||
*/ | |||||
static void clear_table(hash_t *hash) | |||||
{ | |||||
hash_val_t i; | |||||
for (i = 0; i < hash->nchains; i++) | |||||
hash->table[i] = NULL; | |||||
} | |||||
/* | |||||
* Double the size of a dynamic table. This works as follows. Each chain splits | |||||
* into two adjacent chains. The shift amount increases by one, exposing an | |||||
* additional bit of each hashed key. For each node in the original chain, the | |||||
* value of this newly exposed bit will decide which of the two new chains will | |||||
* receive the node: if the bit is 1, the chain with the higher index will have | |||||
* the node, otherwise the lower chain will receive the node. In this manner, | |||||
* the hash table will continue to function exactly as before without having to | |||||
* rehash any of the keys. | |||||
* Notes: | |||||
* 1. Overflow check. | |||||
* 2. The new number of chains is twice the old number of chains. | |||||
* 3. The new mask is one bit wider than the previous, revealing a | |||||
* new bit in all hashed keys. | |||||
* 4. Allocate a new table of chain pointers that is twice as large as the | |||||
* previous one. | |||||
* 5. If the reallocation was successful, we perform the rest of the growth | |||||
* algorithm, otherwise we do nothing. | |||||
* 6. The exposed_bit variable holds a mask with which each hashed key can be | |||||
* AND-ed to test the value of its newly exposed bit. | |||||
* 7. Now loop over each chain in the table and sort its nodes into two | |||||
* chains based on the value of each node's newly exposed hash bit. | |||||
* 8. The low chain replaces the current chain. The high chain goes | |||||
* into the corresponding sister chain in the upper half of the table. | |||||
* 9. We have finished dealing with the chains and nodes. We now update | |||||
* the various bookeeping fields of the hash structure. | |||||
*/ | |||||
static void grow_table(hash_t *hash) | |||||
{ | |||||
hnode_t **newtable; | |||||
assert (2 * hash->nchains > hash->nchains); /* 1 */ | |||||
newtable = realloc(hash->table, | |||||
sizeof *newtable * hash->nchains * 2); /* 4 */ | |||||
if (newtable) { /* 5 */ | |||||
hash_val_t mask = (hash->mask << 1) | 1; /* 3 */ | |||||
hash_val_t exposed_bit = mask ^ hash->mask; /* 6 */ | |||||
hash_val_t chain; | |||||
assert (mask != hash->mask); | |||||
for (chain = 0; chain < hash->nchains; chain++) { /* 7 */ | |||||
hnode_t *low_chain = 0, *high_chain = 0, *hptr, *next; | |||||
for (hptr = newtable[chain]; hptr != 0; hptr = next) { | |||||
next = hptr->next; | |||||
if (hptr->hkey & exposed_bit) { | |||||
hptr->next = high_chain; | |||||
high_chain = hptr; | |||||
} else { | |||||
hptr->next = low_chain; | |||||
low_chain = hptr; | |||||
} | |||||
} | |||||
newtable[chain] = low_chain; /* 8 */ | |||||
newtable[chain + hash->nchains] = high_chain; | |||||
} | |||||
hash->table = newtable; /* 9 */ | |||||
hash->mask = mask; | |||||
hash->nchains *= 2; | |||||
hash->lowmark *= 2; | |||||
hash->highmark *= 2; | |||||
} | |||||
assert (kl_hash_verify(hash)); | |||||
} | |||||
/* | |||||
* Cut a table size in half. This is done by folding together adjacent chains | |||||
* and populating the lower half of the table with these chains. The chains are | |||||
* simply spliced together. Once this is done, the whole table is reallocated | |||||
* to a smaller object. | |||||
* Notes: | |||||
* 1. It is illegal to have a hash table with one slot. This would mean that | |||||
* hash->shift is equal to hash_val_t_bit, an illegal shift value. | |||||
* Also, other things could go wrong, such as hash->lowmark becoming zero. | |||||
* 2. Looping over each pair of sister chains, the low_chain is set to | |||||
* point to the head node of the chain in the lower half of the table, | |||||
* and high_chain points to the head node of the sister in the upper half. | |||||
* 3. The intent here is to compute a pointer to the last node of the | |||||
* lower chain into the low_tail variable. If this chain is empty, | |||||
* low_tail ends up with a null value. | |||||
* 4. If the lower chain is not empty, we simply tack the upper chain onto it. | |||||
* If the upper chain is a null pointer, nothing happens. | |||||
* 5. Otherwise if the lower chain is empty but the upper one is not, | |||||
* If the low chain is empty, but the high chain is not, then the | |||||
* high chain is simply transferred to the lower half of the table. | |||||
* 6. Otherwise if both chains are empty, there is nothing to do. | |||||
* 7. All the chain pointers are in the lower half of the table now, so | |||||
* we reallocate it to a smaller object. This, of course, invalidates | |||||
* all pointer-to-pointers which reference into the table from the | |||||
* first node of each chain. | |||||
* 8. Though it's unlikely, the reallocation may fail. In this case we | |||||
* pretend that the table _was_ reallocated to a smaller object. | |||||
* 9. Finally, update the various table parameters to reflect the new size. | |||||
*/ | |||||
static void shrink_table(hash_t *hash) | |||||
{ | |||||
hash_val_t chain, nchains; | |||||
hnode_t **newtable, *low_tail, *low_chain, *high_chain; | |||||
assert (hash->nchains >= 2); /* 1 */ | |||||
nchains = hash->nchains / 2; | |||||
for (chain = 0; chain < nchains; chain++) { | |||||
low_chain = hash->table[chain]; /* 2 */ | |||||
high_chain = hash->table[chain + nchains]; | |||||
for (low_tail = low_chain; low_tail && low_tail->next; low_tail = low_tail->next) | |||||
; /* 3 */ | |||||
if (low_chain != 0) /* 4 */ | |||||
low_tail->next = high_chain; | |||||
else if (high_chain != 0) /* 5 */ | |||||
hash->table[chain] = high_chain; | |||||
else | |||||
assert (hash->table[chain] == NULL); /* 6 */ | |||||
} | |||||
newtable = realloc(hash->table, | |||||
sizeof *newtable * nchains); /* 7 */ | |||||
if (newtable) /* 8 */ | |||||
hash->table = newtable; | |||||
hash->mask >>= 1; /* 9 */ | |||||
hash->nchains = nchains; | |||||
hash->lowmark /= 2; | |||||
hash->highmark /= 2; | |||||
assert (kl_hash_verify(hash)); | |||||
} | |||||
/* | |||||
* Create a dynamic hash table. Both the hash table structure and the table | |||||
* itself are dynamically allocated. Furthermore, the table is extendible in | |||||
* that it will automatically grow as its load factor increases beyond a | |||||
* certain threshold. | |||||
* Notes: | |||||
* 1. If the number of bits in the hash_val_t type has not been computed yet, | |||||
* we do so here, because this is likely to be the first function that the | |||||
* user calls. | |||||
* 2. Allocate a hash table control structure. | |||||
* 3. If a hash table control structure is successfully allocated, we | |||||
* proceed to initialize it. Otherwise we return a null pointer. | |||||
* 4. We try to allocate the table of hash chains. | |||||
* 5. If we were able to allocate the hash chain table, we can finish | |||||
* initializing the hash structure and the table. Otherwise, we must | |||||
* backtrack by freeing the hash structure. | |||||
* 6. INIT_SIZE should be a power of two. The high and low marks are always set | |||||
* to be twice the table size and half the table size respectively. When the | |||||
* number of nodes in the table grows beyond the high size (beyond load | |||||
* factor 2), it will double in size to cut the load factor down to about | |||||
* about 1. If the table shrinks down to or beneath load factor 0.5, | |||||
* it will shrink, bringing the load up to about 1. However, the table | |||||
* will never shrink beneath INIT_SIZE even if it's emptied. | |||||
* 7. This indicates that the table is dynamically allocated and dynamically | |||||
* resized on the fly. A table that has this value set to zero is | |||||
* assumed to be statically allocated and will not be resized. | |||||
* 8. The table of chains must be properly reset to all null pointers. | |||||
*/ | |||||
hash_t *kl_hash_create(hashcount_t maxcount, hash_comp_t compfun, | |||||
hash_fun_t hashfun) | |||||
{ | |||||
hash_t *hash; | |||||
if (hash_val_t_bit == 0) /* 1 */ | |||||
compute_bits(); | |||||
hash = malloc(sizeof *hash); /* 2 */ | |||||
if (hash) { /* 3 */ | |||||
hash->table = malloc(sizeof *hash->table * INIT_SIZE); /* 4 */ | |||||
if (hash->table) { /* 5 */ | |||||
hash->nchains = INIT_SIZE; /* 6 */ | |||||
hash->highmark = INIT_SIZE * 2; | |||||
hash->lowmark = INIT_SIZE / 2; | |||||
hash->nodecount = 0; | |||||
hash->maxcount = maxcount; | |||||
hash->compare = compfun ? compfun : hash_comp_default; | |||||
hash->function = hashfun ? hashfun : hash_fun_default; | |||||
hash->allocnode = kl_hnode_alloc; | |||||
hash->freenode = kl_hnode_free; | |||||
hash->context = NULL; | |||||
hash->mask = INIT_MASK; | |||||
hash->dynamic = 1; /* 7 */ | |||||
clear_table(hash); /* 8 */ | |||||
assert (kl_hash_verify(hash)); | |||||
return hash; | |||||
} | |||||
free(hash); | |||||
} | |||||
return NULL; | |||||
} | |||||
/* | |||||
* Select a different set of node allocator routines. | |||||
*/ | |||||
void kl_hash_set_allocator(hash_t *hash, hnode_alloc_t al, | |||||
hnode_free_t fr, void *context) | |||||
{ | |||||
assert (kl_hash_count(hash) == 0); | |||||
assert ((al == 0 && fr == 0) || (al != 0 && fr != 0)); | |||||
hash->allocnode = al ? al : kl_hnode_alloc; | |||||
hash->freenode = fr ? fr : kl_hnode_free; | |||||
hash->context = context; | |||||
} | |||||
/* | |||||
* Free every node in the hash using the hash->freenode() function pointer, and | |||||
* cause the hash to become empty. | |||||
*/ | |||||
void kl_hash_free_nodes(hash_t *hash) | |||||
{ | |||||
hscan_t hs; | |||||
hnode_t *node; | |||||
kl_hash_scan_begin(&hs, hash); | |||||
while ((node = kl_hash_scan_next(&hs))) { | |||||
kl_hash_scan_delete(hash, node); | |||||
hash->freenode(node, hash->context); | |||||
} | |||||
hash->nodecount = 0; | |||||
clear_table(hash); | |||||
} | |||||
/* | |||||
* Obsolescent function for removing all nodes from a table, | |||||
* freeing them and then freeing the table all in one step. | |||||
*/ | |||||
void kl_hash_free(hash_t *hash) | |||||
{ | |||||
#ifdef KAZLIB_OBSOLESCENT_DEBUG | |||||
assert ("call to obsolescent function hash_free()" && 0); | |||||
#endif | |||||
kl_hash_free_nodes(hash); | |||||
kl_hash_destroy(hash); | |||||
} | |||||
/* | |||||
* Free a dynamic hash table structure. | |||||
*/ | |||||
void kl_hash_destroy(hash_t *hash) | |||||
{ | |||||
assert (hash_val_t_bit != 0); | |||||
assert (kl_hash_isempty(hash)); | |||||
free(hash->table); | |||||
free(hash); | |||||
} | |||||
/* | |||||
* Initialize a user supplied hash structure. The user also supplies a table of | |||||
* chains which is assigned to the hash structure. The table is static---it | |||||
* will not grow or shrink. | |||||
* 1. See note 1. in hash_create(). | |||||
* 2. The user supplied array of pointers hopefully contains nchains nodes. | |||||
* 3. See note 7. in hash_create(). | |||||
* 4. We must dynamically compute the mask from the given power of two table | |||||
* size. | |||||
* 5. The user supplied table can't be assumed to contain null pointers, | |||||
* so we reset it here. | |||||
*/ | |||||
hash_t *kl_hash_init(hash_t *hash, hashcount_t maxcount, | |||||
hash_comp_t compfun, hash_fun_t hashfun, hnode_t **table, | |||||
hashcount_t nchains) | |||||
{ | |||||
if (hash_val_t_bit == 0) /* 1 */ | |||||
compute_bits(); | |||||
assert (is_power_of_two(nchains)); | |||||
hash->table = table; /* 2 */ | |||||
hash->nchains = nchains; | |||||
hash->nodecount = 0; | |||||
hash->maxcount = maxcount; | |||||
hash->compare = compfun ? compfun : hash_comp_default; | |||||
hash->function = hashfun ? hashfun : hash_fun_default; | |||||
hash->dynamic = 0; /* 3 */ | |||||
hash->mask = compute_mask(nchains); /* 4 */ | |||||
clear_table(hash); /* 5 */ | |||||
assert (kl_hash_verify(hash)); | |||||
return hash; | |||||
} | |||||
/* | |||||
* Reset the hash scanner so that the next element retrieved by | |||||
* hash_scan_next() shall be the first element on the first non-empty chain. | |||||
* Notes: | |||||
* 1. Locate the first non empty chain. | |||||
* 2. If an empty chain is found, remember which one it is and set the next | |||||
* pointer to refer to its first element. | |||||
* 3. Otherwise if a chain is not found, set the next pointer to NULL | |||||
* so that hash_scan_next() shall indicate failure. | |||||
*/ | |||||
void kl_hash_scan_begin(hscan_t *scan, hash_t *hash) | |||||
{ | |||||
hash_val_t nchains = hash->nchains; | |||||
hash_val_t chain; | |||||
scan->table = hash; | |||||
/* 1 */ | |||||
for (chain = 0; chain < nchains && hash->table[chain] == 0; chain++) | |||||
; | |||||
if (chain < nchains) { /* 2 */ | |||||
scan->chain = chain; | |||||
scan->next = hash->table[chain]; | |||||
} else { /* 3 */ | |||||
scan->next = NULL; | |||||
} | |||||
} | |||||
/* | |||||
* Retrieve the next node from the hash table, and update the pointer | |||||
* for the next invocation of hash_scan_next(). | |||||
* Notes: | |||||
* 1. Remember the next pointer in a temporary value so that it can be | |||||
* returned. | |||||
* 2. This assertion essentially checks whether the module has been properly | |||||
* initialized. The first point of interaction with the module should be | |||||
* either hash_create() or hash_init(), both of which set hash_val_t_bit to | |||||
* a non zero value. | |||||
* 3. If the next pointer we are returning is not NULL, then the user is | |||||
* allowed to call hash_scan_next() again. We prepare the new next pointer | |||||
* for that call right now. That way the user is allowed to delete the node | |||||
* we are about to return, since we will no longer be needing it to locate | |||||
* the next node. | |||||
* 4. If there is a next node in the chain (next->next), then that becomes the | |||||
* new next node, otherwise ... | |||||
* 5. We have exhausted the current chain, and must locate the next subsequent | |||||
* non-empty chain in the table. | |||||
* 6. If a non-empty chain is found, the first element of that chain becomes | |||||
* the new next node. Otherwise there is no new next node and we set the | |||||
* pointer to NULL so that the next time hash_scan_next() is called, a null | |||||
* pointer shall be immediately returned. | |||||
*/ | |||||
hnode_t *kl_hash_scan_next(hscan_t *scan) | |||||
{ | |||||
hnode_t *next = scan->next; /* 1 */ | |||||
hash_t *hash = scan->table; | |||||
hash_val_t chain = scan->chain + 1; | |||||
hash_val_t nchains = hash->nchains; | |||||
assert (hash_val_t_bit != 0); /* 2 */ | |||||
if (next) { /* 3 */ | |||||
if (next->next) { /* 4 */ | |||||
scan->next = next->next; | |||||
} else { | |||||
while (chain < nchains && hash->table[chain] == 0) /* 5 */ | |||||
chain++; | |||||
if (chain < nchains) { /* 6 */ | |||||
scan->chain = chain; | |||||
scan->next = hash->table[chain]; | |||||
} else { | |||||
scan->next = NULL; | |||||
} | |||||
} | |||||
} | |||||
return next; | |||||
} | |||||
/* | |||||
* Insert a node into the hash table. | |||||
* Notes: | |||||
* 1. It's illegal to insert more than the maximum number of nodes. The client | |||||
* should verify that the hash table is not full before attempting an | |||||
* insertion. | |||||
* 2. The same key may not be inserted into a table twice. | |||||
* 3. If the table is dynamic and the load factor is already at >= 2, | |||||
* grow the table. | |||||
* 4. We take the bottom N bits of the hash value to derive the chain index, | |||||
* where N is the base 2 logarithm of the size of the hash table. | |||||
*/ | |||||
void kl_hash_insert(hash_t *hash, hnode_t *node, const void *key) | |||||
{ | |||||
hash_val_t hkey, chain; | |||||
assert (hash_val_t_bit != 0); | |||||
assert (node->next == NULL); | |||||
assert (hash->nodecount < hash->maxcount); /* 1 */ | |||||
assert (kl_hash_lookup(hash, key) == NULL); /* 2 */ | |||||
if (hash->dynamic && hash->nodecount >= hash->highmark) /* 3 */ | |||||
grow_table(hash); | |||||
hkey = hash->function(key); | |||||
chain = hkey & hash->mask; /* 4 */ | |||||
node->key = key; | |||||
node->hkey = hkey; | |||||
node->next = hash->table[chain]; | |||||
hash->table[chain] = node; | |||||
hash->nodecount++; | |||||
assert (kl_hash_verify(hash)); | |||||
} | |||||
/* | |||||
* Find a node in the hash table and return a pointer to it. | |||||
* Notes: | |||||
* 1. We hash the key and keep the entire hash value. As an optimization, when | |||||
* we descend down the chain, we can compare hash values first and only if | |||||
* hash values match do we perform a full key comparison. | |||||
* 2. To locate the chain from among 2^N chains, we look at the lower N bits of | |||||
* the hash value by anding them with the current mask. | |||||
* 3. Looping through the chain, we compare the stored hash value inside each | |||||
* node against our computed hash. If they match, then we do a full | |||||
* comparison between the unhashed keys. If these match, we have located the | |||||
* entry. | |||||
*/ | |||||
hnode_t *kl_hash_lookup(hash_t *hash, const void *key) | |||||
{ | |||||
hash_val_t hkey, chain; | |||||
hnode_t *nptr; | |||||
hkey = hash->function(key); /* 1 */ | |||||
chain = hkey & hash->mask; /* 2 */ | |||||
for (nptr = hash->table[chain]; nptr; nptr = nptr->next) { /* 3 */ | |||||
if (nptr->hkey == hkey && hash->compare(nptr->key, key) == 0) | |||||
return nptr; | |||||
} | |||||
return NULL; | |||||
} | |||||
/* | |||||
* Delete the given node from the hash table. Since the chains | |||||
* are singly linked, we must locate the start of the node's chain | |||||
* and traverse. | |||||
* Notes: | |||||
* 1. The node must belong to this hash table, and its key must not have | |||||
* been tampered with. | |||||
* 2. If this deletion will take the node count below the low mark, we | |||||
* shrink the table now. | |||||
* 3. Determine which chain the node belongs to, and fetch the pointer | |||||
* to the first node in this chain. | |||||
* 4. If the node being deleted is the first node in the chain, then | |||||
* simply update the chain head pointer. | |||||
* 5. Otherwise advance to the node's predecessor, and splice out | |||||
* by updating the predecessor's next pointer. | |||||
* 6. Indicate that the node is no longer in a hash table. | |||||
*/ | |||||
hnode_t *kl_hash_delete(hash_t *hash, hnode_t *node) | |||||
{ | |||||
hash_val_t chain; | |||||
hnode_t *hptr; | |||||
assert (kl_hash_lookup(hash, node->key) == node); /* 1 */ | |||||
assert (hash_val_t_bit != 0); | |||||
if (hash->dynamic && hash->nodecount <= hash->lowmark | |||||
&& hash->nodecount > INIT_SIZE) | |||||
shrink_table(hash); /* 2 */ | |||||
chain = node->hkey & hash->mask; /* 3 */ | |||||
hptr = hash->table[chain]; | |||||
if (hptr == node) { /* 4 */ | |||||
hash->table[chain] = node->next; | |||||
} else { | |||||
while (hptr->next != node) { /* 5 */ | |||||
assert (hptr != 0); | |||||
hptr = hptr->next; | |||||
} | |||||
assert (hptr->next == node); | |||||
hptr->next = node->next; | |||||
} | |||||
hash->nodecount--; | |||||
assert (kl_hash_verify(hash)); | |||||
node->next = NULL; /* 6 */ | |||||
return node; | |||||
} | |||||
int kl_hash_alloc_insert(hash_t *hash, const void *key, void *data) | |||||
{ | |||||
hnode_t *node = hash->allocnode(hash->context); | |||||
if (node) { | |||||
kl_hnode_init(node, data); | |||||
kl_hash_insert(hash, node, key); | |||||
return 1; | |||||
} | |||||
return 0; | |||||
} | |||||
void kl_hash_delete_free(hash_t *hash, hnode_t *node) | |||||
{ | |||||
kl_hash_delete(hash, node); | |||||
hash->freenode(node, hash->context); | |||||
} | |||||
/* | |||||
* Exactly like hash_delete, except does not trigger table shrinkage. This is to be | |||||
* used from within a hash table scan operation. See notes for hash_delete. | |||||
*/ | |||||
hnode_t *kl_hash_scan_delete(hash_t *hash, hnode_t *node) | |||||
{ | |||||
hash_val_t chain; | |||||
hnode_t *hptr; | |||||
assert (kl_hash_lookup(hash, node->key) == node); | |||||
assert (hash_val_t_bit != 0); | |||||
chain = node->hkey & hash->mask; | |||||
hptr = hash->table[chain]; | |||||
if (hptr == node) { | |||||
hash->table[chain] = node->next; | |||||
} else { | |||||
while (hptr->next != node) | |||||
hptr = hptr->next; | |||||
hptr->next = node->next; | |||||
} | |||||
hash->nodecount--; | |||||
assert (kl_hash_verify(hash)); | |||||
node->next = NULL; | |||||
return node; | |||||
} | |||||
/* | |||||
* Like hash_delete_free but based on hash_scan_delete. | |||||
*/ | |||||
void kl_hash_scan_delfree(hash_t *hash, hnode_t *node) | |||||
{ | |||||
kl_hash_scan_delete(hash, node); | |||||
hash->freenode(node, hash->context); | |||||
} | |||||
/* | |||||
* Verify whether the given object is a valid hash table. This means | |||||
* Notes: | |||||
* 1. If the hash table is dynamic, verify whether the high and | |||||
* low expansion/shrinkage thresholds are powers of two. | |||||
* 2. Count all nodes in the table, and test each hash value | |||||
* to see whether it is correct for the node's chain. | |||||
*/ | |||||
int kl_hash_verify(hash_t *hash) | |||||
{ | |||||
hashcount_t count = 0; | |||||
hash_val_t chain; | |||||
hnode_t *hptr; | |||||
if (hash->dynamic) { /* 1 */ | |||||
if (hash->lowmark >= hash->highmark) | |||||
return 0; | |||||
if (!is_power_of_two(hash->highmark)) | |||||
return 0; | |||||
if (!is_power_of_two(hash->lowmark)) | |||||
return 0; | |||||
} | |||||
for (chain = 0; chain < hash->nchains; chain++) { /* 2 */ | |||||
for (hptr = hash->table[chain]; hptr != 0; hptr = hptr->next) { | |||||
if ((hptr->hkey & hash->mask) != chain) | |||||
return 0; | |||||
count++; | |||||
} | |||||
} | |||||
if (count != hash->nodecount) | |||||
return 0; | |||||
return 1; | |||||
} | |||||
/* | |||||
* Test whether the hash table is full and return 1 if this is true, | |||||
* 0 if it is false. | |||||
*/ | |||||
#undef kl_hash_isfull | |||||
int kl_hash_isfull(hash_t *hash) | |||||
{ | |||||
return hash->nodecount == hash->maxcount; | |||||
} | |||||
/* | |||||
* Test whether the hash table is empty and return 1 if this is true, | |||||
* 0 if it is false. | |||||
*/ | |||||
#undef kl_hash_isempty | |||||
int kl_hash_isempty(hash_t *hash) | |||||
{ | |||||
return hash->nodecount == 0; | |||||
} | |||||
static hnode_t *kl_hnode_alloc(void *context) | |||||
{ | |||||
return malloc(sizeof *kl_hnode_alloc(NULL)); | |||||
} | |||||
static void kl_hnode_free(hnode_t *node, void *context) | |||||
{ | |||||
free(node); | |||||
} | |||||
/* | |||||
* Create a hash table node dynamically and assign it the given data. | |||||
*/ | |||||
hnode_t *kl_hnode_create(void *data) | |||||
{ | |||||
hnode_t *node = malloc(sizeof *node); | |||||
if (node) { | |||||
node->data = data; | |||||
node->next = NULL; | |||||
} | |||||
return node; | |||||
} | |||||
/* | |||||
* Initialize a client-supplied node | |||||
*/ | |||||
hnode_t *kl_hnode_init(hnode_t *hnode, void *data) | |||||
{ | |||||
hnode->data = data; | |||||
hnode->next = NULL; | |||||
return hnode; | |||||
} | |||||
/* | |||||
* Destroy a dynamically allocated node. | |||||
*/ | |||||
void kl_hnode_destroy(hnode_t *hnode) | |||||
{ | |||||
free(hnode); | |||||
} | |||||
#undef kl_hnode_put | |||||
void kl_hnode_put(hnode_t *node, void *data) | |||||
{ | |||||
node->data = data; | |||||
} | |||||
#undef kl_hnode_get | |||||
void *kl_hnode_get(hnode_t *node) | |||||
{ | |||||
return node->data; | |||||
} | |||||
#undef kl_hnode_getkey | |||||
const void *kl_hnode_getkey(hnode_t *node) | |||||
{ | |||||
return node->key; | |||||
} | |||||
#undef kl_hash_count | |||||
hashcount_t kl_hash_count(hash_t *hash) | |||||
{ | |||||
return hash->nodecount; | |||||
} | |||||
#undef kl_hash_size | |||||
hashcount_t kl_hash_size(hash_t *hash) | |||||
{ | |||||
return hash->nchains; | |||||
} | |||||
static hash_val_t hash_fun_default(const void *key) | |||||
{ | |||||
static unsigned long randbox[] = { | |||||
0x49848f1bU, 0xe6255dbaU, 0x36da5bdcU, 0x47bf94e9U, | |||||
0x8cbcce22U, 0x559fc06aU, 0xd268f536U, 0xe10af79aU, | |||||
0xc1af4d69U, 0x1d2917b5U, 0xec4c304dU, 0x9ee5016cU, | |||||
0x69232f74U, 0xfead7bb3U, 0xe9089ab6U, 0xf012f6aeU, | |||||
}; | |||||
const unsigned char *str = key; | |||||
hash_val_t acc = 0; | |||||
while (*str) { | |||||
acc ^= randbox[(*str + acc) & 0xf]; | |||||
acc = (acc << 1) | (acc >> 31); | |||||
acc &= 0xffffffffU; | |||||
acc ^= randbox[((*str++ >> 4) + acc) & 0xf]; | |||||
acc = (acc << 2) | (acc >> 30); | |||||
acc &= 0xffffffffU; | |||||
} | |||||
return acc; | |||||
} | |||||
static int hash_comp_default(const void *key1, const void *key2) | |||||
{ | |||||
return strcmp(key1, key2); | |||||
} |
@ -0,0 +1,240 @@ | |||||
/* | |||||
* Hash Table Data Type | |||||
* Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net> | |||||
* | |||||
* Free Software License: | |||||
* | |||||
* All rights are reserved by the author, with the following exceptions: | |||||
* Permission is granted to freely reproduce and distribute this software, | |||||
* possibly in exchange for a fee, provided that this copyright notice appears | |||||
* intact. Permission is also granted to adapt this software to produce | |||||
* derivative works, as long as the modified versions carry this copyright | |||||
* notice and additional notices stating that the work has been modified. | |||||
* This source code may be translated into executable form and incorporated | |||||
* into proprietary software; there is no requirement for such software to | |||||
* contain a copyright notice related to this source. | |||||
* | |||||
* $Id: hash.h,v 1.22.2.7 2000/11/13 01:36:45 kaz Exp $ | |||||
* $Name: kazlib_1_20 $ | |||||
*/ | |||||
#ifndef HASH_H | |||||
#define HASH_H | |||||
#include <limits.h> | |||||
#ifdef KAZLIB_SIDEEFFECT_DEBUG | |||||
#include "sfx.h" | |||||
#endif | |||||
/* | |||||
* Blurb for inclusion into C++ translation units | |||||
*/ | |||||
#ifdef __cplusplus | |||||
extern "C" { | |||||
#endif | |||||
typedef unsigned long hashcount_t; | |||||
#define HASHCOUNT_T_MAX ULONG_MAX | |||||
typedef unsigned long hash_val_t; | |||||
#define HASH_VAL_T_MAX ULONG_MAX | |||||
extern int hash_val_t_bit; | |||||
#ifndef HASH_VAL_T_BIT | |||||
#define HASH_VAL_T_BIT ((int) hash_val_t_bit) | |||||
#endif | |||||
/* | |||||
* Hash chain node structure. | |||||
* Notes: | |||||
* 1. This preprocessing directive is for debugging purposes. The effect is | |||||
* that if the preprocessor symbol KAZLIB_OPAQUE_DEBUG is defined prior to the | |||||
* inclusion of this header, then the structure shall be declared as having | |||||
* the single member int __OPAQUE__. This way, any attempts by the | |||||
* client code to violate the principles of information hiding (by accessing | |||||
* the structure directly) can be diagnosed at translation time. However, | |||||
* note the resulting compiled unit is not suitable for linking. | |||||
* 2. This is a pointer to the next node in the chain. In the last node of a | |||||
* chain, this pointer is null. | |||||
* 3. The key is a pointer to some user supplied data that contains a unique | |||||
* identifier for each hash node in a given table. The interpretation of | |||||
* the data is up to the user. When creating or initializing a hash table, | |||||
* the user must supply a pointer to a function for comparing two keys, | |||||
* and a pointer to a function for hashing a key into a numeric value. | |||||
* 4. The value is a user-supplied pointer to void which may refer to | |||||
* any data object. It is not interpreted in any way by the hashing | |||||
* module. | |||||
* 5. The hashed key is stored in each node so that we don't have to rehash | |||||
* each key when the table must grow or shrink. | |||||
*/ | |||||
typedef struct hnode_t { | |||||
#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) /* 1 */ | |||||
struct hnode_t *hash_next; /* 2 */ | |||||
const void *hash_key; /* 3 */ | |||||
void *hash_data; /* 4 */ | |||||
hash_val_t hash_hkey; /* 5 */ | |||||
#else | |||||
int hash_dummy; | |||||
#endif | |||||
} hnode_t; | |||||
/* | |||||
* The comparison function pointer type. A comparison function takes two keys | |||||
* and produces a value of -1 if the left key is less than the right key, a | |||||
* value of 0 if the keys are equal, and a value of 1 if the left key is | |||||
* greater than the right key. | |||||
*/ | |||||
typedef int (*hash_comp_t)(const void *, const void *); | |||||
/* | |||||
* The hashing function performs some computation on a key and produces an | |||||
* integral value of type hash_val_t based on that key. For best results, the | |||||
* function should have a good randomness properties in *all* significant bits | |||||
* over the set of keys that are being inserted into a given hash table. In | |||||
* particular, the most significant bits of hash_val_t are most significant to | |||||
* the hash module. Only as the hash table expands are less significant bits | |||||
* examined. Thus a function that has good distribution in its upper bits but | |||||
* not lower is preferrable to one that has poor distribution in the upper bits | |||||
* but not the lower ones. | |||||
*/ | |||||
typedef hash_val_t (*hash_fun_t)(const void *); | |||||
/* | |||||
* allocator functions | |||||
*/ | |||||
typedef hnode_t *(*hnode_alloc_t)(void *); | |||||
typedef void (*hnode_free_t)(hnode_t *, void *); | |||||
/* | |||||
* This is the hash table control structure. It keeps track of information | |||||
* about a hash table, as well as the hash table itself. | |||||
* Notes: | |||||
* 1. Pointer to the hash table proper. The table is an array of pointers to | |||||
* hash nodes (of type hnode_t). If the table is empty, every element of | |||||
* this table is a null pointer. A non-null entry points to the first | |||||
* element of a chain of nodes. | |||||
* 2. This member keeps track of the size of the hash table---that is, the | |||||
* number of chain pointers. | |||||
* 3. The count member maintains the number of elements that are presently | |||||
* in the hash table. | |||||
* 4. The maximum count is the greatest number of nodes that can populate this | |||||
* table. If the table contains this many nodes, no more can be inserted, | |||||
* and the hash_isfull() function returns true. | |||||
* 5. The high mark is a population threshold, measured as a number of nodes, | |||||
* which, if exceeded, will trigger a table expansion. Only dynamic hash | |||||
* tables are subject to this expansion. | |||||
* 6. The low mark is a minimum population threshold, measured as a number of | |||||
* nodes. If the table population drops below this value, a table shrinkage | |||||
* will occur. Only dynamic tables are subject to this reduction. No table | |||||
* will shrink beneath a certain absolute minimum number of nodes. | |||||
* 7. This is the a pointer to the hash table's comparison function. The | |||||
* function is set once at initialization or creation time. | |||||
* 8. Pointer to the table's hashing function, set once at creation or | |||||
* initialization time. | |||||
* 9. The current hash table mask. If the size of the hash table is 2^N, | |||||
* this value has its low N bits set to 1, and the others clear. It is used | |||||
* to select bits from the result of the hashing function to compute an | |||||
* index into the table. | |||||
* 10. A flag which indicates whether the table is to be dynamically resized. It | |||||
* is set to 1 in dynamically allocated tables, 0 in tables that are | |||||
* statically allocated. | |||||
*/ | |||||
typedef struct hash_t { | |||||
#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) | |||||
struct hnode_t **hash_table; /* 1 */ | |||||
hashcount_t hash_nchains; /* 2 */ | |||||
hashcount_t hash_nodecount; /* 3 */ | |||||
hashcount_t hash_maxcount; /* 4 */ | |||||
hashcount_t hash_highmark; /* 5 */ | |||||
hashcount_t hash_lowmark; /* 6 */ | |||||
hash_comp_t hash_compare; /* 7 */ | |||||
hash_fun_t hash_function; /* 8 */ | |||||
hnode_alloc_t hash_allocnode; | |||||
hnode_free_t hash_freenode; | |||||
void *hash_context; | |||||
hash_val_t hash_mask; /* 9 */ | |||||
int hash_dynamic; /* 10 */ | |||||
#else | |||||
int hash_dummy; | |||||
#endif | |||||
} hash_t; | |||||
/* | |||||
* Hash scanner structure, used for traversals of the data structure. | |||||
* Notes: | |||||
* 1. Pointer to the hash table that is being traversed. | |||||
* 2. Reference to the current chain in the table being traversed (the chain | |||||
* that contains the next node that shall be retrieved). | |||||
* 3. Pointer to the node that will be retrieved by the subsequent call to | |||||
* hash_scan_next(). | |||||
*/ | |||||
typedef struct hscan_t { | |||||
#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) | |||||
hash_t *hash_table; /* 1 */ | |||||
hash_val_t hash_chain; /* 2 */ | |||||
hnode_t *hash_next; /* 3 */ | |||||
#else | |||||
int hash_dummy; | |||||
#endif | |||||
} hscan_t; | |||||
extern hash_t *kl_hash_create(hashcount_t, hash_comp_t, hash_fun_t); | |||||
extern void kl_hash_set_allocator(hash_t *, hnode_alloc_t, hnode_free_t, void *); | |||||
extern void kl_hash_destroy(hash_t *); | |||||
extern void kl_hash_free_nodes(hash_t *); | |||||
extern void kl_hash_free(hash_t *); | |||||
extern hash_t *kl_hash_init(hash_t *, hashcount_t, hash_comp_t, | |||||
hash_fun_t, hnode_t **, hashcount_t); | |||||
extern void kl_hash_insert(hash_t *, hnode_t *, const void *); | |||||
extern hnode_t *kl_hash_lookup(hash_t *, const void *); | |||||
extern hnode_t *kl_hash_delete(hash_t *, hnode_t *); | |||||
extern int kl_hash_alloc_insert(hash_t *, const void *, void *); | |||||
extern void kl_hash_delete_free(hash_t *, hnode_t *); | |||||
extern void kl_hnode_put(hnode_t *, void *); | |||||
extern void *kl_hnode_get(hnode_t *); | |||||
extern const void *kl_hnode_getkey(hnode_t *); | |||||
extern hashcount_t kl_hash_count(hash_t *); | |||||
extern hashcount_t kl_hash_size(hash_t *); | |||||
extern int kl_hash_isfull(hash_t *); | |||||
extern int kl_hash_isempty(hash_t *); | |||||
extern void kl_hash_scan_begin(hscan_t *, hash_t *); | |||||
extern hnode_t *kl_hash_scan_next(hscan_t *); | |||||
extern hnode_t *kl_hash_scan_delete(hash_t *, hnode_t *); | |||||
extern void kl_hash_scan_delfree(hash_t *, hnode_t *); | |||||
extern int kl_hash_verify(hash_t *); | |||||
extern hnode_t *kl_hnode_create(void *); | |||||
extern hnode_t *kl_hnode_init(hnode_t *, void *); | |||||
extern void kl_hnode_destroy(hnode_t *); | |||||
#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) | |||||
#ifdef KAZLIB_SIDEEFFECT_DEBUG | |||||
#define kl_hash_isfull(H) (SFX_CHECK(H)->hash_nodecount == (H)->hash_maxcount) | |||||
#else | |||||
#define kl_hash_isfull(H) ((H)->hash_nodecount == (H)->hash_maxcount) | |||||
#endif | |||||
#define kl_hash_isempty(H) ((H)->hash_nodecount == 0) | |||||
#define kl_hash_count(H) ((H)->hash_nodecount) | |||||
#define kl_hash_size(H) ((H)->hash_nchains) | |||||
#define kl_hnode_get(N) ((N)->hash_data) | |||||
#define kl_hnode_getkey(N) ((N)->hash_key) | |||||
#define kl_hnode_put(N, V) ((N)->hash_data = (V)) | |||||
#endif | |||||
#ifdef __cplusplus | |||||
} | |||||
#endif | |||||
#endif |
@ -0,0 +1,658 @@ | |||||
// This file is part of khash released under the MIT license. | |||||
// See the LICENSE file for more information. | |||||
// Copyright 2013 Cloudant, Inc <support@cloudant.com> | |||||
#include <assert.h> | |||||
#include <string.h> | |||||
#include <stdint.h> | |||||
#include "erl_nif.h" | |||||
#include "hash.h" | |||||
#ifdef _WIN32 | |||||
#define INLINE __inline | |||||
#else | |||||
#define INLINE inline | |||||
#endif | |||||
#define KHASH_VERSION 0 | |||||
typedef struct | |||||
{ | |||||
ERL_NIF_TERM atom_ok; | |||||
ERL_NIF_TERM atom_error; | |||||
ERL_NIF_TERM atom_value; | |||||
ERL_NIF_TERM atom_not_found; | |||||
ERL_NIF_TERM atom_end_of_table; | |||||
ERL_NIF_TERM atom_expired_iterator; | |||||
ErlNifResourceType* res_hash; | |||||
ErlNifResourceType* res_iter; | |||||
} khash_priv; | |||||
typedef struct | |||||
{ | |||||
unsigned int hval; | |||||
ErlNifEnv* env; | |||||
ERL_NIF_TERM key; | |||||
ERL_NIF_TERM val; | |||||
} khnode_t; | |||||
typedef struct | |||||
{ | |||||
int version; | |||||
unsigned int gen; | |||||
hash_t* h; | |||||
ErlNifPid p; | |||||
} khash_t; | |||||
typedef struct | |||||
{ | |||||
int version; | |||||
unsigned int gen; | |||||
khash_t* khash; | |||||
hscan_t scan; | |||||
} khash_iter_t; | |||||
static INLINE ERL_NIF_TERM | |||||
make_atom(ErlNifEnv* env, const char* name) | |||||
{ | |||||
ERL_NIF_TERM ret; | |||||
if(enif_make_existing_atom(env, name, &ret, ERL_NIF_LATIN1)) { | |||||
return ret; | |||||
} | |||||
return enif_make_atom(env, name); | |||||
} | |||||
static INLINE ERL_NIF_TERM | |||||
make_ok(ErlNifEnv* env, khash_priv* priv, ERL_NIF_TERM value) | |||||
{ | |||||
return enif_make_tuple2(env, priv->atom_ok, value); | |||||
} | |||||
static INLINE ERL_NIF_TERM | |||||
make_error(ErlNifEnv* env, khash_priv* priv, ERL_NIF_TERM reason) | |||||
{ | |||||
return enif_make_tuple2(env, priv->atom_error, reason); | |||||
} | |||||
static INLINE int | |||||
check_pid(ErlNifEnv* env, khash_t* khash) | |||||
{ | |||||
ErlNifPid pid; | |||||
enif_self(env, &pid); | |||||
if(enif_compare(pid.pid, khash->p.pid) == 0) { | |||||
return 1; | |||||
} | |||||
return 0; | |||||
} | |||||
hnode_t* | |||||
khnode_alloc(void* ctx) | |||||
{ | |||||
hnode_t* ret = (hnode_t*) enif_alloc(sizeof(hnode_t)); | |||||
khnode_t* node = (khnode_t*) enif_alloc(sizeof(khnode_t)); | |||||
memset(ret, '\0', sizeof(hnode_t)); | |||||
memset(node, '\0', sizeof(khnode_t)); | |||||
node->env = enif_alloc_env(); | |||||
ret->hash_key = node; | |||||
return ret; | |||||
} | |||||
void | |||||
khnode_free(hnode_t* obj, void* ctx) | |||||
{ | |||||
khnode_t* node = (khnode_t*) kl_hnode_getkey(obj); | |||||
enif_free_env(node->env); | |||||
enif_free(node); | |||||
enif_free(obj); | |||||
return; | |||||
} | |||||
int | |||||
khash_cmp_fun(const void* l, const void* r) | |||||
{ | |||||
khnode_t* left = (khnode_t*) l; | |||||
khnode_t* right = (khnode_t*) r; | |||||
int cmp = enif_compare(left->key, right->key); | |||||
if(cmp < 0) { | |||||
return -1; | |||||
} else if(cmp == 0) { | |||||
return 0; | |||||
} else { | |||||
return 1; | |||||
} | |||||
} | |||||
hash_val_t | |||||
khash_hash_fun(const void* obj) | |||||
{ | |||||
khnode_t* node = (khnode_t*) obj; | |||||
return (hash_val_t) node->hval; | |||||
} | |||||
static INLINE khash_t* | |||||
khash_create_int(ErlNifEnv* env, khash_priv* priv, ERL_NIF_TERM opts) | |||||
{ | |||||
khash_t* khash = NULL; | |||||
assert(priv != NULL && "missing private data member"); | |||||
khash = (khash_t*) enif_alloc_resource(priv->res_hash, sizeof(khash_t)); | |||||
memset(khash, '\0', sizeof(khash_t)); | |||||
khash->version = KHASH_VERSION; | |||||
khash->gen = 0; | |||||
khash->h = kl_hash_create(HASHCOUNT_T_MAX, khash_cmp_fun, khash_hash_fun); | |||||
if(khash->h == NULL ) { | |||||
enif_release_resource(khash); | |||||
return NULL; | |||||
} | |||||
kl_hash_set_allocator(khash->h, khnode_alloc, khnode_free, NULL); | |||||
enif_self(env, &(khash->p)); | |||||
return khash; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = khash_create_int(env, priv, argv[0]); | |||||
if(khash == NULL) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_resource(env, khash); | |||||
enif_release_resource(khash); | |||||
return make_ok(env, priv, ret); | |||||
} | |||||
static void | |||||
khash_free(ErlNifEnv* env, void* obj) | |||||
{ | |||||
khash_t* khash = (khash_t*) obj; | |||||
if(khash->h != NULL) { | |||||
kl_hash_free_nodes(khash->h); | |||||
kl_hash_destroy(khash->h); | |||||
} | |||||
return; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_to_list(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = (khash_priv*) enif_priv_data(env); | |||||
ERL_NIF_TERM ret = enif_make_list(env, 0); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
hscan_t scan; | |||||
hnode_t* entry; | |||||
khnode_t* node; | |||||
ERL_NIF_TERM key; | |||||
ERL_NIF_TERM val; | |||||
ERL_NIF_TERM tuple; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
kl_hash_scan_begin(&scan, khash->h); | |||||
while((entry = kl_hash_scan_next(&scan)) != NULL) { | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
key = enif_make_copy(env, node->key); | |||||
val = enif_make_copy(env, node->val); | |||||
tuple = enif_make_tuple2(env, key, val); | |||||
ret = enif_make_list_cell(env, tuple, ret); | |||||
} | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_clear(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
kl_hash_free_nodes(khash->h); | |||||
khash->gen += 1; | |||||
return priv->atom_ok; | |||||
} | |||||
static INLINE hnode_t* | |||||
khash_lookup_int(ErlNifEnv* env, uint32_t hv, ERL_NIF_TERM key, khash_t* khash) | |||||
{ | |||||
khnode_t node; | |||||
node.hval = hv; | |||||
node.env = env; | |||||
node.key = key; | |||||
return kl_hash_lookup(khash->h, &node); | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_lookup(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
uint32_t hval; | |||||
hnode_t* entry; | |||||
khnode_t* node; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 3) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &hval)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
entry = khash_lookup_int(env, hval, argv[2], khash); | |||||
if(entry == NULL) { | |||||
ret = priv->atom_not_found; | |||||
} else { | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
ret = enif_make_copy(env, node->val); | |||||
ret = enif_make_tuple2(env, priv->atom_value, ret); | |||||
} | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_get(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
uint32_t hval; | |||||
hnode_t* entry; | |||||
khnode_t* node; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 4) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &hval)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
entry = khash_lookup_int(env, hval, argv[2], khash); | |||||
if(entry == NULL) { | |||||
ret = argv[3]; | |||||
} else { | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
ret = enif_make_copy(env, node->val); | |||||
} | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_put(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
uint32_t hval; | |||||
hnode_t* entry; | |||||
khnode_t* node; | |||||
if(argc != 4) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &hval)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
entry = khash_lookup_int(env, hval, argv[2], khash); | |||||
if(entry == NULL) { | |||||
entry = khnode_alloc(NULL); | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
node->hval = hval; | |||||
node->key = enif_make_copy(node->env, argv[2]); | |||||
node->val = enif_make_copy(node->env, argv[3]); | |||||
kl_hash_insert(khash->h, entry, node); | |||||
} else { | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
enif_clear_env(node->env); | |||||
node->key = enif_make_copy(node->env, argv[2]); | |||||
node->val = enif_make_copy(node->env, argv[3]); | |||||
} | |||||
khash->gen += 1; | |||||
return priv->atom_ok; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_del(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
uint32_t hval; | |||||
hnode_t* entry; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 3) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &hval)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
entry = khash_lookup_int(env, hval, argv[2], khash); | |||||
if(entry == NULL) { | |||||
ret = priv->atom_not_found; | |||||
} else { | |||||
kl_hash_delete_free(khash->h, entry); | |||||
ret = priv->atom_ok; | |||||
} | |||||
khash->gen += 1; | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, (void*) &khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
return enif_make_uint64(env, kl_hash_count(khash->h)); | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
khash_iter_t* iter; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
iter = (khash_iter_t*) enif_alloc_resource( | |||||
priv->res_iter, sizeof(khash_iter_t)); | |||||
memset(iter, '\0', sizeof(khash_iter_t)); | |||||
iter->version = KHASH_VERSION; | |||||
iter->gen = khash->gen; | |||||
iter->khash = khash; | |||||
kl_hash_scan_begin(&(iter->scan), iter->khash->h); | |||||
// The iterator needs to guarantee that the khash | |||||
// remains alive for the life of the iterator. | |||||
enif_keep_resource(khash); | |||||
ret = enif_make_resource(env, iter); | |||||
enif_release_resource(iter); | |||||
return make_ok(env, priv, ret); | |||||
} | |||||
static void | |||||
khash_iter_free(ErlNifEnv* env, void* obj) | |||||
{ | |||||
khash_iter_t* iter = (khash_iter_t*) obj; | |||||
enif_release_resource(iter->khash); | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_iter_next(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_iter_t* iter = NULL; | |||||
void* res = NULL; | |||||
hnode_t* entry; | |||||
khnode_t* node; | |||||
ERL_NIF_TERM key; | |||||
ERL_NIF_TERM val; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_iter, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
iter = (khash_iter_t*) res; | |||||
if(!check_pid(env, iter->khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(iter->gen != iter->khash->gen) { | |||||
return make_error(env, priv, priv->atom_expired_iterator); | |||||
} | |||||
entry = kl_hash_scan_next(&(iter->scan)); | |||||
if(entry == NULL) { | |||||
return priv->atom_end_of_table; | |||||
} | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
key = enif_make_copy(env, node->key); | |||||
val = enif_make_copy(env, node->val); | |||||
return enif_make_tuple2(env, key, val); | |||||
} | |||||
static int | |||||
load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) | |||||
{ | |||||
int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; | |||||
ErlNifResourceType* res; | |||||
khash_priv* new_priv = (khash_priv*) enif_alloc(sizeof(khash_priv)); | |||||
if(new_priv == NULL) { | |||||
return 1; | |||||
} | |||||
res = enif_open_resource_type( | |||||
env, NULL, "khash", khash_free, flags, NULL); | |||||
if(res == NULL) { | |||||
return 1; | |||||
} | |||||
new_priv->res_hash = res; | |||||
res = enif_open_resource_type( | |||||
env, NULL, "khash_iter", khash_iter_free, flags, NULL); | |||||
if(res == NULL) { | |||||
return 1; | |||||
} | |||||
new_priv->res_iter = res; | |||||
new_priv->atom_ok = make_atom(env, "ok"); | |||||
new_priv->atom_error = make_atom(env, "error"); | |||||
new_priv->atom_value = make_atom(env, "value"); | |||||
new_priv->atom_not_found = make_atom(env, "not_found"); | |||||
new_priv->atom_end_of_table = make_atom(env, "end_of_table"); | |||||
new_priv->atom_expired_iterator = make_atom(env, "expired_iterator"); | |||||
*priv = (void*) new_priv; | |||||
return 0; | |||||
} | |||||
static int | |||||
reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) | |||||
{ | |||||
return 0; | |||||
} | |||||
static int | |||||
upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) | |||||
{ | |||||
return load(env, priv, info); | |||||
} | |||||
static void | |||||
unload(ErlNifEnv* env, void* priv) | |||||
{ | |||||
enif_free(priv); | |||||
return; | |||||
} | |||||
static ErlNifFunc funcs[] = { | |||||
{"new", 1, khash_new}, | |||||
{"to_list", 1, khash_to_list}, | |||||
{"clear", 1, khash_clear}, | |||||
{"lookup_int", 3, khash_lookup}, | |||||
{"get_int", 4, khash_get}, | |||||
{"put_int", 4, khash_put}, | |||||
{"del_int", 3, khash_del}, | |||||
{"size", 1, khash_size}, | |||||
{"iter", 1, khash_iter}, | |||||
{"iter_next", 1, khash_iter_next} | |||||
}; | |||||
ERL_NIF_INIT(khash, funcs, &load, &reload, &upgrade, &unload); |
@ -0,0 +1,12 @@ | |||||
{port_specs, [ | |||||
{"../../priv/khash.so", ["*.c"]} | |||||
]}. | |||||
{port_env, [ | |||||
% Development compilation | |||||
% {".*", "CFLAGS", "$CFLAGS -g -Wall -Werror -fPIC"} | |||||
% Production compilation | |||||
{"(linux|solaris|darwin|freebsd)", "CFLAGS", "$CFLAGS -Wall -Werror -DNDEBUG -O3"}, | |||||
{"win32", "CFLAGS", "$CFLAGS /O2 /DNDEBUG /Wall"} | |||||
]}. |
@ -0,0 +1,843 @@ | |||||
/* | |||||
* Hash Table Data Type | |||||
* Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net> | |||||
* | |||||
* Free Software License: | |||||
* | |||||
* All rights are reserved by the author, with the following exceptions: | |||||
* Permission is granted to freely reproduce and distribute this software, | |||||
* possibly in exchange for a fee, provided that this copyright notice appears | |||||
* intact. Permission is also granted to adapt this software to produce | |||||
* derivative works, as long as the modified versions carry this copyright | |||||
* notice and additional notices stating that the work has been modified. | |||||
* This source code may be translated into executable form and incorporated | |||||
* into proprietary software; there is no requirement for such software to | |||||
* contain a copyright notice related to this source. | |||||
* | |||||
* $Id: hash.c,v 1.36.2.11 2000/11/13 01:36:45 kaz Exp $ | |||||
* $Name: kazlib_1_20 $ | |||||
*/ | |||||
#include <stdlib.h> | |||||
#include <stddef.h> | |||||
#include <assert.h> | |||||
#include <string.h> | |||||
#define HASH_IMPLEMENTATION | |||||
#include "hash.h" | |||||
#ifdef KAZLIB_RCSID | |||||
static const char rcsid[] = "$Id: hash.c,v 1.36.2.11 2000/11/13 01:36:45 kaz Exp $"; | |||||
#endif | |||||
#define INIT_BITS 6 | |||||
#define INIT_SIZE (1UL << (INIT_BITS)) /* must be power of two */ | |||||
#define INIT_MASK ((INIT_SIZE) - 1) | |||||
#define next hash_next | |||||
#define key hash_key | |||||
#define data hash_data | |||||
#define hkey hash_hkey | |||||
#define table hash_table | |||||
#define nchains hash_nchains | |||||
#define nodecount hash_nodecount | |||||
#define maxcount hash_maxcount | |||||
#define highmark hash_highmark | |||||
#define lowmark hash_lowmark | |||||
#define compare hash_compare | |||||
#define function hash_function | |||||
#define allocnode hash_allocnode | |||||
#define freenode hash_freenode | |||||
#define context hash_context | |||||
#define mask hash_mask | |||||
#define dynamic hash_dynamic | |||||
#define table hash_table | |||||
#define chain hash_chain | |||||
static hnode_t *kl_hnode_alloc(void *context); | |||||
static void kl_hnode_free(hnode_t *node, void *context); | |||||
static hash_val_t hash_fun_default(const void *key); | |||||
static int hash_comp_default(const void *key1, const void *key2); | |||||
int hash_val_t_bit; | |||||
/* | |||||
* Compute the number of bits in the hash_val_t type. We know that hash_val_t | |||||
* is an unsigned integral type. Thus the highest value it can hold is a | |||||
* Mersenne number (power of two, less one). We initialize a hash_val_t | |||||
* object with this value and then shift bits out one by one while counting. | |||||
* Notes: | |||||
* 1. HASH_VAL_T_MAX is a Mersenne number---one that is one less than a power | |||||
* of two. This means that its binary representation consists of all one | |||||
* bits, and hence ``val'' is initialized to all one bits. | |||||
* 2. While bits remain in val, we increment the bit count and shift it to the | |||||
* right, replacing the topmost bit by zero. | |||||
*/ | |||||
static void compute_bits(void) | |||||
{ | |||||
hash_val_t val = HASH_VAL_T_MAX; /* 1 */ | |||||
int bits = 0; | |||||
while (val) { /* 2 */ | |||||
bits++; | |||||
val >>= 1; | |||||
} | |||||
hash_val_t_bit = bits; | |||||
} | |||||
/* | |||||
* Verify whether the given argument is a power of two. | |||||
*/ | |||||
static int is_power_of_two(hash_val_t arg) | |||||
{ | |||||
if (arg == 0) | |||||
return 0; | |||||
while ((arg & 1) == 0) | |||||
arg >>= 1; | |||||
return (arg == 1); | |||||
} | |||||
/* | |||||
* Compute a shift amount from a given table size | |||||
*/ | |||||
static hash_val_t compute_mask(hashcount_t size) | |||||
{ | |||||
assert (is_power_of_two(size)); | |||||
assert (size >= 2); | |||||
return size - 1; | |||||
} | |||||
/* | |||||
* Initialize the table of pointers to null. | |||||
*/ | |||||
static void clear_table(hash_t *hash) | |||||
{ | |||||
hash_val_t i; | |||||
for (i = 0; i < hash->nchains; i++) | |||||
hash->table[i] = NULL; | |||||
} | |||||
/* | |||||
* Double the size of a dynamic table. This works as follows. Each chain splits | |||||
* into two adjacent chains. The shift amount increases by one, exposing an | |||||
* additional bit of each hashed key. For each node in the original chain, the | |||||
* value of this newly exposed bit will decide which of the two new chains will | |||||
* receive the node: if the bit is 1, the chain with the higher index will have | |||||
* the node, otherwise the lower chain will receive the node. In this manner, | |||||
* the hash table will continue to function exactly as before without having to | |||||
* rehash any of the keys. | |||||
* Notes: | |||||
* 1. Overflow check. | |||||
* 2. The new number of chains is twice the old number of chains. | |||||
* 3. The new mask is one bit wider than the previous, revealing a | |||||
* new bit in all hashed keys. | |||||
* 4. Allocate a new table of chain pointers that is twice as large as the | |||||
* previous one. | |||||
* 5. If the reallocation was successful, we perform the rest of the growth | |||||
* algorithm, otherwise we do nothing. | |||||
* 6. The exposed_bit variable holds a mask with which each hashed key can be | |||||
* AND-ed to test the value of its newly exposed bit. | |||||
* 7. Now loop over each chain in the table and sort its nodes into two | |||||
* chains based on the value of each node's newly exposed hash bit. | |||||
* 8. The low chain replaces the current chain. The high chain goes | |||||
* into the corresponding sister chain in the upper half of the table. | |||||
* 9. We have finished dealing with the chains and nodes. We now update | |||||
* the various bookeeping fields of the hash structure. | |||||
*/ | |||||
static void grow_table(hash_t *hash) | |||||
{ | |||||
hnode_t **newtable; | |||||
assert (2 * hash->nchains > hash->nchains); /* 1 */ | |||||
newtable = realloc(hash->table, | |||||
sizeof *newtable * hash->nchains * 2); /* 4 */ | |||||
if (newtable) { /* 5 */ | |||||
hash_val_t mask = (hash->mask << 1) | 1; /* 3 */ | |||||
hash_val_t exposed_bit = mask ^ hash->mask; /* 6 */ | |||||
hash_val_t chain; | |||||
assert (mask != hash->mask); | |||||
for (chain = 0; chain < hash->nchains; chain++) { /* 7 */ | |||||
hnode_t *low_chain = 0, *high_chain = 0, *hptr, *next; | |||||
for (hptr = newtable[chain]; hptr != 0; hptr = next) { | |||||
next = hptr->next; | |||||
if (hptr->hkey & exposed_bit) { | |||||
hptr->next = high_chain; | |||||
high_chain = hptr; | |||||
} else { | |||||
hptr->next = low_chain; | |||||
low_chain = hptr; | |||||
} | |||||
} | |||||
newtable[chain] = low_chain; /* 8 */ | |||||
newtable[chain + hash->nchains] = high_chain; | |||||
} | |||||
hash->table = newtable; /* 9 */ | |||||
hash->mask = mask; | |||||
hash->nchains *= 2; | |||||
hash->lowmark *= 2; | |||||
hash->highmark *= 2; | |||||
} | |||||
assert (kl_hash_verify(hash)); | |||||
} | |||||
/* | |||||
* Cut a table size in half. This is done by folding together adjacent chains | |||||
* and populating the lower half of the table with these chains. The chains are | |||||
* simply spliced together. Once this is done, the whole table is reallocated | |||||
* to a smaller object. | |||||
* Notes: | |||||
* 1. It is illegal to have a hash table with one slot. This would mean that | |||||
* hash->shift is equal to hash_val_t_bit, an illegal shift value. | |||||
* Also, other things could go wrong, such as hash->lowmark becoming zero. | |||||
* 2. Looping over each pair of sister chains, the low_chain is set to | |||||
* point to the head node of the chain in the lower half of the table, | |||||
* and high_chain points to the head node of the sister in the upper half. | |||||
* 3. The intent here is to compute a pointer to the last node of the | |||||
* lower chain into the low_tail variable. If this chain is empty, | |||||
* low_tail ends up with a null value. | |||||
* 4. If the lower chain is not empty, we simply tack the upper chain onto it. | |||||
* If the upper chain is a null pointer, nothing happens. | |||||
* 5. Otherwise if the lower chain is empty but the upper one is not, | |||||
* If the low chain is empty, but the high chain is not, then the | |||||
* high chain is simply transferred to the lower half of the table. | |||||
* 6. Otherwise if both chains are empty, there is nothing to do. | |||||
* 7. All the chain pointers are in the lower half of the table now, so | |||||
* we reallocate it to a smaller object. This, of course, invalidates | |||||
* all pointer-to-pointers which reference into the table from the | |||||
* first node of each chain. | |||||
* 8. Though it's unlikely, the reallocation may fail. In this case we | |||||
* pretend that the table _was_ reallocated to a smaller object. | |||||
* 9. Finally, update the various table parameters to reflect the new size. | |||||
*/ | |||||
static void shrink_table(hash_t *hash) | |||||
{ | |||||
hash_val_t chain, nchains; | |||||
hnode_t **newtable, *low_tail, *low_chain, *high_chain; | |||||
assert (hash->nchains >= 2); /* 1 */ | |||||
nchains = hash->nchains / 2; | |||||
for (chain = 0; chain < nchains; chain++) { | |||||
low_chain = hash->table[chain]; /* 2 */ | |||||
high_chain = hash->table[chain + nchains]; | |||||
for (low_tail = low_chain; low_tail && low_tail->next; low_tail = low_tail->next) | |||||
; /* 3 */ | |||||
if (low_chain != 0) /* 4 */ | |||||
low_tail->next = high_chain; | |||||
else if (high_chain != 0) /* 5 */ | |||||
hash->table[chain] = high_chain; | |||||
else | |||||
assert (hash->table[chain] == NULL); /* 6 */ | |||||
} | |||||
newtable = realloc(hash->table, | |||||
sizeof *newtable * nchains); /* 7 */ | |||||
if (newtable) /* 8 */ | |||||
hash->table = newtable; | |||||
hash->mask >>= 1; /* 9 */ | |||||
hash->nchains = nchains; | |||||
hash->lowmark /= 2; | |||||
hash->highmark /= 2; | |||||
assert (kl_hash_verify(hash)); | |||||
} | |||||
/* | |||||
* Create a dynamic hash table. Both the hash table structure and the table | |||||
* itself are dynamically allocated. Furthermore, the table is extendible in | |||||
* that it will automatically grow as its load factor increases beyond a | |||||
* certain threshold. | |||||
* Notes: | |||||
* 1. If the number of bits in the hash_val_t type has not been computed yet, | |||||
* we do so here, because this is likely to be the first function that the | |||||
* user calls. | |||||
* 2. Allocate a hash table control structure. | |||||
* 3. If a hash table control structure is successfully allocated, we | |||||
* proceed to initialize it. Otherwise we return a null pointer. | |||||
* 4. We try to allocate the table of hash chains. | |||||
* 5. If we were able to allocate the hash chain table, we can finish | |||||
* initializing the hash structure and the table. Otherwise, we must | |||||
* backtrack by freeing the hash structure. | |||||
* 6. INIT_SIZE should be a power of two. The high and low marks are always set | |||||
* to be twice the table size and half the table size respectively. When the | |||||
* number of nodes in the table grows beyond the high size (beyond load | |||||
* factor 2), it will double in size to cut the load factor down to about | |||||
* about 1. If the table shrinks down to or beneath load factor 0.5, | |||||
* it will shrink, bringing the load up to about 1. However, the table | |||||
* will never shrink beneath INIT_SIZE even if it's emptied. | |||||
* 7. This indicates that the table is dynamically allocated and dynamically | |||||
* resized on the fly. A table that has this value set to zero is | |||||
* assumed to be statically allocated and will not be resized. | |||||
* 8. The table of chains must be properly reset to all null pointers. | |||||
*/ | |||||
hash_t *kl_hash_create(hashcount_t maxcount, hash_comp_t compfun, | |||||
hash_fun_t hashfun) | |||||
{ | |||||
hash_t *hash; | |||||
if (hash_val_t_bit == 0) /* 1 */ | |||||
compute_bits(); | |||||
hash = malloc(sizeof *hash); /* 2 */ | |||||
if (hash) { /* 3 */ | |||||
hash->table = malloc(sizeof *hash->table * INIT_SIZE); /* 4 */ | |||||
if (hash->table) { /* 5 */ | |||||
hash->nchains = INIT_SIZE; /* 6 */ | |||||
hash->highmark = INIT_SIZE * 2; | |||||
hash->lowmark = INIT_SIZE / 2; | |||||
hash->nodecount = 0; | |||||
hash->maxcount = maxcount; | |||||
hash->compare = compfun ? compfun : hash_comp_default; | |||||
hash->function = hashfun ? hashfun : hash_fun_default; | |||||
hash->allocnode = kl_hnode_alloc; | |||||
hash->freenode = kl_hnode_free; | |||||
hash->context = NULL; | |||||
hash->mask = INIT_MASK; | |||||
hash->dynamic = 1; /* 7 */ | |||||
clear_table(hash); /* 8 */ | |||||
assert (kl_hash_verify(hash)); | |||||
return hash; | |||||
} | |||||
free(hash); | |||||
} | |||||
return NULL; | |||||
} | |||||
/* | |||||
* Select a different set of node allocator routines. | |||||
*/ | |||||
void kl_hash_set_allocator(hash_t *hash, hnode_alloc_t al, | |||||
hnode_free_t fr, void *context) | |||||
{ | |||||
assert (kl_hash_count(hash) == 0); | |||||
assert ((al == 0 && fr == 0) || (al != 0 && fr != 0)); | |||||
hash->allocnode = al ? al : kl_hnode_alloc; | |||||
hash->freenode = fr ? fr : kl_hnode_free; | |||||
hash->context = context; | |||||
} | |||||
/* | |||||
* Free every node in the hash using the hash->freenode() function pointer, and | |||||
* cause the hash to become empty. | |||||
*/ | |||||
void kl_hash_free_nodes(hash_t *hash) | |||||
{ | |||||
hscan_t hs; | |||||
hnode_t *node; | |||||
kl_hash_scan_begin(&hs, hash); | |||||
while ((node = kl_hash_scan_next(&hs))) { | |||||
kl_hash_scan_delete(hash, node); | |||||
hash->freenode(node, hash->context); | |||||
} | |||||
hash->nodecount = 0; | |||||
clear_table(hash); | |||||
} | |||||
/* | |||||
* Obsolescent function for removing all nodes from a table, | |||||
* freeing them and then freeing the table all in one step. | |||||
*/ | |||||
void kl_hash_free(hash_t *hash) | |||||
{ | |||||
#ifdef KAZLIB_OBSOLESCENT_DEBUG | |||||
assert ("call to obsolescent function hash_free()" && 0); | |||||
#endif | |||||
kl_hash_free_nodes(hash); | |||||
kl_hash_destroy(hash); | |||||
} | |||||
/* | |||||
* Free a dynamic hash table structure. | |||||
*/ | |||||
void kl_hash_destroy(hash_t *hash) | |||||
{ | |||||
assert (hash_val_t_bit != 0); | |||||
assert (kl_hash_isempty(hash)); | |||||
free(hash->table); | |||||
free(hash); | |||||
} | |||||
/* | |||||
* Initialize a user supplied hash structure. The user also supplies a table of | |||||
* chains which is assigned to the hash structure. The table is static---it | |||||
* will not grow or shrink. | |||||
* 1. See note 1. in hash_create(). | |||||
* 2. The user supplied array of pointers hopefully contains nchains nodes. | |||||
* 3. See note 7. in hash_create(). | |||||
* 4. We must dynamically compute the mask from the given power of two table | |||||
* size. | |||||
* 5. The user supplied table can't be assumed to contain null pointers, | |||||
* so we reset it here. | |||||
*/ | |||||
hash_t *kl_hash_init(hash_t *hash, hashcount_t maxcount, | |||||
hash_comp_t compfun, hash_fun_t hashfun, hnode_t **table, | |||||
hashcount_t nchains) | |||||
{ | |||||
if (hash_val_t_bit == 0) /* 1 */ | |||||
compute_bits(); | |||||
assert (is_power_of_two(nchains)); | |||||
hash->table = table; /* 2 */ | |||||
hash->nchains = nchains; | |||||
hash->nodecount = 0; | |||||
hash->maxcount = maxcount; | |||||
hash->compare = compfun ? compfun : hash_comp_default; | |||||
hash->function = hashfun ? hashfun : hash_fun_default; | |||||
hash->dynamic = 0; /* 3 */ | |||||
hash->mask = compute_mask(nchains); /* 4 */ | |||||
clear_table(hash); /* 5 */ | |||||
assert (kl_hash_verify(hash)); | |||||
return hash; | |||||
} | |||||
/* | |||||
* Reset the hash scanner so that the next element retrieved by | |||||
* hash_scan_next() shall be the first element on the first non-empty chain. | |||||
* Notes: | |||||
* 1. Locate the first non empty chain. | |||||
* 2. If an empty chain is found, remember which one it is and set the next | |||||
* pointer to refer to its first element. | |||||
* 3. Otherwise if a chain is not found, set the next pointer to NULL | |||||
* so that hash_scan_next() shall indicate failure. | |||||
*/ | |||||
void kl_hash_scan_begin(hscan_t *scan, hash_t *hash) | |||||
{ | |||||
hash_val_t nchains = hash->nchains; | |||||
hash_val_t chain; | |||||
scan->table = hash; | |||||
/* 1 */ | |||||
for (chain = 0; chain < nchains && hash->table[chain] == 0; chain++) | |||||
; | |||||
if (chain < nchains) { /* 2 */ | |||||
scan->chain = chain; | |||||
scan->next = hash->table[chain]; | |||||
} else { /* 3 */ | |||||
scan->next = NULL; | |||||
} | |||||
} | |||||
/* | |||||
* Retrieve the next node from the hash table, and update the pointer | |||||
* for the next invocation of hash_scan_next(). | |||||
* Notes: | |||||
* 1. Remember the next pointer in a temporary value so that it can be | |||||
* returned. | |||||
* 2. This assertion essentially checks whether the module has been properly | |||||
* initialized. The first point of interaction with the module should be | |||||
* either hash_create() or hash_init(), both of which set hash_val_t_bit to | |||||
* a non zero value. | |||||
* 3. If the next pointer we are returning is not NULL, then the user is | |||||
* allowed to call hash_scan_next() again. We prepare the new next pointer | |||||
* for that call right now. That way the user is allowed to delete the node | |||||
* we are about to return, since we will no longer be needing it to locate | |||||
* the next node. | |||||
* 4. If there is a next node in the chain (next->next), then that becomes the | |||||
* new next node, otherwise ... | |||||
* 5. We have exhausted the current chain, and must locate the next subsequent | |||||
* non-empty chain in the table. | |||||
* 6. If a non-empty chain is found, the first element of that chain becomes | |||||
* the new next node. Otherwise there is no new next node and we set the | |||||
* pointer to NULL so that the next time hash_scan_next() is called, a null | |||||
* pointer shall be immediately returned. | |||||
*/ | |||||
hnode_t *kl_hash_scan_next(hscan_t *scan) | |||||
{ | |||||
hnode_t *next = scan->next; /* 1 */ | |||||
hash_t *hash = scan->table; | |||||
hash_val_t chain = scan->chain + 1; | |||||
hash_val_t nchains = hash->nchains; | |||||
assert (hash_val_t_bit != 0); /* 2 */ | |||||
if (next) { /* 3 */ | |||||
if (next->next) { /* 4 */ | |||||
scan->next = next->next; | |||||
} else { | |||||
while (chain < nchains && hash->table[chain] == 0) /* 5 */ | |||||
chain++; | |||||
if (chain < nchains) { /* 6 */ | |||||
scan->chain = chain; | |||||
scan->next = hash->table[chain]; | |||||
} else { | |||||
scan->next = NULL; | |||||
} | |||||
} | |||||
} | |||||
return next; | |||||
} | |||||
/* | |||||
* Insert a node into the hash table. | |||||
* Notes: | |||||
* 1. It's illegal to insert more than the maximum number of nodes. The client | |||||
* should verify that the hash table is not full before attempting an | |||||
* insertion. | |||||
* 2. The same key may not be inserted into a table twice. | |||||
* 3. If the table is dynamic and the load factor is already at >= 2, | |||||
* grow the table. | |||||
* 4. We take the bottom N bits of the hash value to derive the chain index, | |||||
* where N is the base 2 logarithm of the size of the hash table. | |||||
*/ | |||||
void kl_hash_insert(hash_t *hash, hnode_t *node, const void *key) | |||||
{ | |||||
hash_val_t hkey, chain; | |||||
assert (hash_val_t_bit != 0); | |||||
assert (node->next == NULL); | |||||
assert (hash->nodecount < hash->maxcount); /* 1 */ | |||||
assert (kl_hash_lookup(hash, key) == NULL); /* 2 */ | |||||
if (hash->dynamic && hash->nodecount >= hash->highmark) /* 3 */ | |||||
grow_table(hash); | |||||
hkey = hash->function(key); | |||||
chain = hkey & hash->mask; /* 4 */ | |||||
node->key = key; | |||||
node->hkey = hkey; | |||||
node->next = hash->table[chain]; | |||||
hash->table[chain] = node; | |||||
hash->nodecount++; | |||||
assert (kl_hash_verify(hash)); | |||||
} | |||||
/* | |||||
* Find a node in the hash table and return a pointer to it. | |||||
* Notes: | |||||
* 1. We hash the key and keep the entire hash value. As an optimization, when | |||||
* we descend down the chain, we can compare hash values first and only if | |||||
* hash values match do we perform a full key comparison. | |||||
* 2. To locate the chain from among 2^N chains, we look at the lower N bits of | |||||
* the hash value by anding them with the current mask. | |||||
* 3. Looping through the chain, we compare the stored hash value inside each | |||||
* node against our computed hash. If they match, then we do a full | |||||
* comparison between the unhashed keys. If these match, we have located the | |||||
* entry. | |||||
*/ | |||||
hnode_t *kl_hash_lookup(hash_t *hash, const void *key) | |||||
{ | |||||
hash_val_t hkey, chain; | |||||
hnode_t *nptr; | |||||
hkey = hash->function(key); /* 1 */ | |||||
chain = hkey & hash->mask; /* 2 */ | |||||
for (nptr = hash->table[chain]; nptr; nptr = nptr->next) { /* 3 */ | |||||
if (nptr->hkey == hkey && hash->compare(nptr->key, key) == 0) | |||||
return nptr; | |||||
} | |||||
return NULL; | |||||
} | |||||
/* | |||||
* Delete the given node from the hash table. Since the chains | |||||
* are singly linked, we must locate the start of the node's chain | |||||
* and traverse. | |||||
* Notes: | |||||
* 1. The node must belong to this hash table, and its key must not have | |||||
* been tampered with. | |||||
* 2. If this deletion will take the node count below the low mark, we | |||||
* shrink the table now. | |||||
* 3. Determine which chain the node belongs to, and fetch the pointer | |||||
* to the first node in this chain. | |||||
* 4. If the node being deleted is the first node in the chain, then | |||||
* simply update the chain head pointer. | |||||
* 5. Otherwise advance to the node's predecessor, and splice out | |||||
* by updating the predecessor's next pointer. | |||||
* 6. Indicate that the node is no longer in a hash table. | |||||
*/ | |||||
hnode_t *kl_hash_delete(hash_t *hash, hnode_t *node) | |||||
{ | |||||
hash_val_t chain; | |||||
hnode_t *hptr; | |||||
assert (kl_hash_lookup(hash, node->key) == node); /* 1 */ | |||||
assert (hash_val_t_bit != 0); | |||||
if (hash->dynamic && hash->nodecount <= hash->lowmark | |||||
&& hash->nodecount > INIT_SIZE) | |||||
shrink_table(hash); /* 2 */ | |||||
chain = node->hkey & hash->mask; /* 3 */ | |||||
hptr = hash->table[chain]; | |||||
if (hptr == node) { /* 4 */ | |||||
hash->table[chain] = node->next; | |||||
} else { | |||||
while (hptr->next != node) { /* 5 */ | |||||
assert (hptr != 0); | |||||
hptr = hptr->next; | |||||
} | |||||
assert (hptr->next == node); | |||||
hptr->next = node->next; | |||||
} | |||||
hash->nodecount--; | |||||
assert (kl_hash_verify(hash)); | |||||
node->next = NULL; /* 6 */ | |||||
return node; | |||||
} | |||||
int kl_hash_alloc_insert(hash_t *hash, const void *key, void *data) | |||||
{ | |||||
hnode_t *node = hash->allocnode(hash->context); | |||||
if (node) { | |||||
kl_hnode_init(node, data); | |||||
kl_hash_insert(hash, node, key); | |||||
return 1; | |||||
} | |||||
return 0; | |||||
} | |||||
void kl_hash_delete_free(hash_t *hash, hnode_t *node) | |||||
{ | |||||
kl_hash_delete(hash, node); | |||||
hash->freenode(node, hash->context); | |||||
} | |||||
/* | |||||
* Exactly like hash_delete, except does not trigger table shrinkage. This is to be | |||||
* used from within a hash table scan operation. See notes for hash_delete. | |||||
*/ | |||||
hnode_t *kl_hash_scan_delete(hash_t *hash, hnode_t *node) | |||||
{ | |||||
hash_val_t chain; | |||||
hnode_t *hptr; | |||||
assert (kl_hash_lookup(hash, node->key) == node); | |||||
assert (hash_val_t_bit != 0); | |||||
chain = node->hkey & hash->mask; | |||||
hptr = hash->table[chain]; | |||||
if (hptr == node) { | |||||
hash->table[chain] = node->next; | |||||
} else { | |||||
while (hptr->next != node) | |||||
hptr = hptr->next; | |||||
hptr->next = node->next; | |||||
} | |||||
hash->nodecount--; | |||||
assert (kl_hash_verify(hash)); | |||||
node->next = NULL; | |||||
return node; | |||||
} | |||||
/* | |||||
* Like hash_delete_free but based on hash_scan_delete. | |||||
*/ | |||||
void kl_hash_scan_delfree(hash_t *hash, hnode_t *node) | |||||
{ | |||||
kl_hash_scan_delete(hash, node); | |||||
hash->freenode(node, hash->context); | |||||
} | |||||
/* | |||||
* Verify whether the given object is a valid hash table. This means | |||||
* Notes: | |||||
* 1. If the hash table is dynamic, verify whether the high and | |||||
* low expansion/shrinkage thresholds are powers of two. | |||||
* 2. Count all nodes in the table, and test each hash value | |||||
* to see whether it is correct for the node's chain. | |||||
*/ | |||||
int kl_hash_verify(hash_t *hash) | |||||
{ | |||||
hashcount_t count = 0; | |||||
hash_val_t chain; | |||||
hnode_t *hptr; | |||||
if (hash->dynamic) { /* 1 */ | |||||
if (hash->lowmark >= hash->highmark) | |||||
return 0; | |||||
if (!is_power_of_two(hash->highmark)) | |||||
return 0; | |||||
if (!is_power_of_two(hash->lowmark)) | |||||
return 0; | |||||
} | |||||
for (chain = 0; chain < hash->nchains; chain++) { /* 2 */ | |||||
for (hptr = hash->table[chain]; hptr != 0; hptr = hptr->next) { | |||||
if ((hptr->hkey & hash->mask) != chain) | |||||
return 0; | |||||
count++; | |||||
} | |||||
} | |||||
if (count != hash->nodecount) | |||||
return 0; | |||||
return 1; | |||||
} | |||||
/* | |||||
* Test whether the hash table is full and return 1 if this is true, | |||||
* 0 if it is false. | |||||
*/ | |||||
#undef kl_hash_isfull | |||||
int kl_hash_isfull(hash_t *hash) | |||||
{ | |||||
return hash->nodecount == hash->maxcount; | |||||
} | |||||
/* | |||||
* Test whether the hash table is empty and return 1 if this is true, | |||||
* 0 if it is false. | |||||
*/ | |||||
#undef kl_hash_isempty | |||||
int kl_hash_isempty(hash_t *hash) | |||||
{ | |||||
return hash->nodecount == 0; | |||||
} | |||||
static hnode_t *kl_hnode_alloc(void *context) | |||||
{ | |||||
return malloc(sizeof *kl_hnode_alloc(NULL)); | |||||
} | |||||
static void kl_hnode_free(hnode_t *node, void *context) | |||||
{ | |||||
free(node); | |||||
} | |||||
/* | |||||
* Create a hash table node dynamically and assign it the given data. | |||||
*/ | |||||
hnode_t *kl_hnode_create(void *data) | |||||
{ | |||||
hnode_t *node = malloc(sizeof *node); | |||||
if (node) { | |||||
node->data = data; | |||||
node->next = NULL; | |||||
} | |||||
return node; | |||||
} | |||||
/* | |||||
* Initialize a client-supplied node | |||||
*/ | |||||
hnode_t *kl_hnode_init(hnode_t *hnode, void *data) | |||||
{ | |||||
hnode->data = data; | |||||
hnode->next = NULL; | |||||
return hnode; | |||||
} | |||||
/* | |||||
* Destroy a dynamically allocated node. | |||||
*/ | |||||
void kl_hnode_destroy(hnode_t *hnode) | |||||
{ | |||||
free(hnode); | |||||
} | |||||
#undef kl_hnode_put | |||||
void kl_hnode_put(hnode_t *node, void *data) | |||||
{ | |||||
node->data = data; | |||||
} | |||||
#undef kl_hnode_get | |||||
void *kl_hnode_get(hnode_t *node) | |||||
{ | |||||
return node->data; | |||||
} | |||||
#undef kl_hnode_getkey | |||||
const void *kl_hnode_getkey(hnode_t *node) | |||||
{ | |||||
return node->key; | |||||
} | |||||
#undef kl_hash_count | |||||
hashcount_t kl_hash_count(hash_t *hash) | |||||
{ | |||||
return hash->nodecount; | |||||
} | |||||
#undef kl_hash_size | |||||
hashcount_t kl_hash_size(hash_t *hash) | |||||
{ | |||||
return hash->nchains; | |||||
} | |||||
static hash_val_t hash_fun_default(const void *key) | |||||
{ | |||||
static unsigned long randbox[] = { | |||||
0x49848f1bU, 0xe6255dbaU, 0x36da5bdcU, 0x47bf94e9U, | |||||
0x8cbcce22U, 0x559fc06aU, 0xd268f536U, 0xe10af79aU, | |||||
0xc1af4d69U, 0x1d2917b5U, 0xec4c304dU, 0x9ee5016cU, | |||||
0x69232f74U, 0xfead7bb3U, 0xe9089ab6U, 0xf012f6aeU, | |||||
}; | |||||
const unsigned char *str = key; | |||||
hash_val_t acc = 0; | |||||
while (*str) { | |||||
acc ^= randbox[(*str + acc) & 0xf]; | |||||
acc = (acc << 1) | (acc >> 31); | |||||
acc &= 0xffffffffU; | |||||
acc ^= randbox[((*str++ >> 4) + acc) & 0xf]; | |||||
acc = (acc << 2) | (acc >> 30); | |||||
acc &= 0xffffffffU; | |||||
} | |||||
return acc; | |||||
} | |||||
static int hash_comp_default(const void *key1, const void *key2) | |||||
{ | |||||
return strcmp(key1, key2); | |||||
} |
@ -0,0 +1,240 @@ | |||||
/* | |||||
* Hash Table Data Type | |||||
* Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net> | |||||
* | |||||
* Free Software License: | |||||
* | |||||
* All rights are reserved by the author, with the following exceptions: | |||||
* Permission is granted to freely reproduce and distribute this software, | |||||
* possibly in exchange for a fee, provided that this copyright notice appears | |||||
* intact. Permission is also granted to adapt this software to produce | |||||
* derivative works, as long as the modified versions carry this copyright | |||||
* notice and additional notices stating that the work has been modified. | |||||
* This source code may be translated into executable form and incorporated | |||||
* into proprietary software; there is no requirement for such software to | |||||
* contain a copyright notice related to this source. | |||||
* | |||||
* $Id: hash.h,v 1.22.2.7 2000/11/13 01:36:45 kaz Exp $ | |||||
* $Name: kazlib_1_20 $ | |||||
*/ | |||||
#ifndef HASH_H | |||||
#define HASH_H | |||||
#include <limits.h> | |||||
#ifdef KAZLIB_SIDEEFFECT_DEBUG | |||||
#include "sfx.h" | |||||
#endif | |||||
/* | |||||
* Blurb for inclusion into C++ translation units | |||||
*/ | |||||
#ifdef __cplusplus | |||||
extern "C" { | |||||
#endif | |||||
typedef unsigned long hashcount_t; | |||||
#define HASHCOUNT_T_MAX ULONG_MAX | |||||
typedef unsigned long hash_val_t; | |||||
#define HASH_VAL_T_MAX ULONG_MAX | |||||
extern int hash_val_t_bit; | |||||
#ifndef HASH_VAL_T_BIT | |||||
#define HASH_VAL_T_BIT ((int) hash_val_t_bit) | |||||
#endif | |||||
/* | |||||
* Hash chain node structure. | |||||
* Notes: | |||||
* 1. This preprocessing directive is for debugging purposes. The effect is | |||||
* that if the preprocessor symbol KAZLIB_OPAQUE_DEBUG is defined prior to the | |||||
* inclusion of this header, then the structure shall be declared as having | |||||
* the single member int __OPAQUE__. This way, any attempts by the | |||||
* client code to violate the principles of information hiding (by accessing | |||||
* the structure directly) can be diagnosed at translation time. However, | |||||
* note the resulting compiled unit is not suitable for linking. | |||||
* 2. This is a pointer to the next node in the chain. In the last node of a | |||||
* chain, this pointer is null. | |||||
* 3. The key is a pointer to some user supplied data that contains a unique | |||||
* identifier for each hash node in a given table. The interpretation of | |||||
* the data is up to the user. When creating or initializing a hash table, | |||||
* the user must supply a pointer to a function for comparing two keys, | |||||
* and a pointer to a function for hashing a key into a numeric value. | |||||
* 4. The value is a user-supplied pointer to void which may refer to | |||||
* any data object. It is not interpreted in any way by the hashing | |||||
* module. | |||||
* 5. The hashed key is stored in each node so that we don't have to rehash | |||||
* each key when the table must grow or shrink. | |||||
*/ | |||||
typedef struct hnode_t { | |||||
#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) /* 1 */ | |||||
struct hnode_t *hash_next; /* 2 */ | |||||
const void *hash_key; /* 3 */ | |||||
void *hash_data; /* 4 */ | |||||
hash_val_t hash_hkey; /* 5 */ | |||||
#else | |||||
int hash_dummy; | |||||
#endif | |||||
} hnode_t; | |||||
/* | |||||
* The comparison function pointer type. A comparison function takes two keys | |||||
* and produces a value of -1 if the left key is less than the right key, a | |||||
* value of 0 if the keys are equal, and a value of 1 if the left key is | |||||
* greater than the right key. | |||||
*/ | |||||
typedef int (*hash_comp_t)(const void *, const void *); | |||||
/* | |||||
* The hashing function performs some computation on a key and produces an | |||||
* integral value of type hash_val_t based on that key. For best results, the | |||||
* function should have a good randomness properties in *all* significant bits | |||||
* over the set of keys that are being inserted into a given hash table. In | |||||
* particular, the most significant bits of hash_val_t are most significant to | |||||
* the hash module. Only as the hash table expands are less significant bits | |||||
* examined. Thus a function that has good distribution in its upper bits but | |||||
* not lower is preferrable to one that has poor distribution in the upper bits | |||||
* but not the lower ones. | |||||
*/ | |||||
typedef hash_val_t (*hash_fun_t)(const void *); | |||||
/* | |||||
* allocator functions | |||||
*/ | |||||
typedef hnode_t *(*hnode_alloc_t)(void *); | |||||
typedef void (*hnode_free_t)(hnode_t *, void *); | |||||
/* | |||||
* This is the hash table control structure. It keeps track of information | |||||
* about a hash table, as well as the hash table itself. | |||||
* Notes: | |||||
* 1. Pointer to the hash table proper. The table is an array of pointers to | |||||
* hash nodes (of type hnode_t). If the table is empty, every element of | |||||
* this table is a null pointer. A non-null entry points to the first | |||||
* element of a chain of nodes. | |||||
* 2. This member keeps track of the size of the hash table---that is, the | |||||
* number of chain pointers. | |||||
* 3. The count member maintains the number of elements that are presently | |||||
* in the hash table. | |||||
* 4. The maximum count is the greatest number of nodes that can populate this | |||||
* table. If the table contains this many nodes, no more can be inserted, | |||||
* and the hash_isfull() function returns true. | |||||
* 5. The high mark is a population threshold, measured as a number of nodes, | |||||
* which, if exceeded, will trigger a table expansion. Only dynamic hash | |||||
* tables are subject to this expansion. | |||||
* 6. The low mark is a minimum population threshold, measured as a number of | |||||
* nodes. If the table population drops below this value, a table shrinkage | |||||
* will occur. Only dynamic tables are subject to this reduction. No table | |||||
* will shrink beneath a certain absolute minimum number of nodes. | |||||
* 7. This is the a pointer to the hash table's comparison function. The | |||||
* function is set once at initialization or creation time. | |||||
* 8. Pointer to the table's hashing function, set once at creation or | |||||
* initialization time. | |||||
* 9. The current hash table mask. If the size of the hash table is 2^N, | |||||
* this value has its low N bits set to 1, and the others clear. It is used | |||||
* to select bits from the result of the hashing function to compute an | |||||
* index into the table. | |||||
* 10. A flag which indicates whether the table is to be dynamically resized. It | |||||
* is set to 1 in dynamically allocated tables, 0 in tables that are | |||||
* statically allocated. | |||||
*/ | |||||
typedef struct hash_t { | |||||
#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) | |||||
struct hnode_t **hash_table; /* 1 */ | |||||
hashcount_t hash_nchains; /* 2 */ | |||||
hashcount_t hash_nodecount; /* 3 */ | |||||
hashcount_t hash_maxcount; /* 4 */ | |||||
hashcount_t hash_highmark; /* 5 */ | |||||
hashcount_t hash_lowmark; /* 6 */ | |||||
hash_comp_t hash_compare; /* 7 */ | |||||
hash_fun_t hash_function; /* 8 */ | |||||
hnode_alloc_t hash_allocnode; | |||||
hnode_free_t hash_freenode; | |||||
void *hash_context; | |||||
hash_val_t hash_mask; /* 9 */ | |||||
int hash_dynamic; /* 10 */ | |||||
#else | |||||
int hash_dummy; | |||||
#endif | |||||
} hash_t; | |||||
/* | |||||
* Hash scanner structure, used for traversals of the data structure. | |||||
* Notes: | |||||
* 1. Pointer to the hash table that is being traversed. | |||||
* 2. Reference to the current chain in the table being traversed (the chain | |||||
* that contains the next node that shall be retrieved). | |||||
* 3. Pointer to the node that will be retrieved by the subsequent call to | |||||
* hash_scan_next(). | |||||
*/ | |||||
typedef struct hscan_t { | |||||
#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) | |||||
hash_t *hash_table; /* 1 */ | |||||
hash_val_t hash_chain; /* 2 */ | |||||
hnode_t *hash_next; /* 3 */ | |||||
#else | |||||
int hash_dummy; | |||||
#endif | |||||
} hscan_t; | |||||
extern hash_t *kl_hash_create(hashcount_t, hash_comp_t, hash_fun_t); | |||||
extern void kl_hash_set_allocator(hash_t *, hnode_alloc_t, hnode_free_t, void *); | |||||
extern void kl_hash_destroy(hash_t *); | |||||
extern void kl_hash_free_nodes(hash_t *); | |||||
extern void kl_hash_free(hash_t *); | |||||
extern hash_t *kl_hash_init(hash_t *, hashcount_t, hash_comp_t, | |||||
hash_fun_t, hnode_t **, hashcount_t); | |||||
extern void kl_hash_insert(hash_t *, hnode_t *, const void *); | |||||
extern hnode_t *kl_hash_lookup(hash_t *, const void *); | |||||
extern hnode_t *kl_hash_delete(hash_t *, hnode_t *); | |||||
extern int kl_hash_alloc_insert(hash_t *, const void *, void *); | |||||
extern void kl_hash_delete_free(hash_t *, hnode_t *); | |||||
extern void kl_hnode_put(hnode_t *, void *); | |||||
extern void *kl_hnode_get(hnode_t *); | |||||
extern const void *kl_hnode_getkey(hnode_t *); | |||||
extern hashcount_t kl_hash_count(hash_t *); | |||||
extern hashcount_t kl_hash_size(hash_t *); | |||||
extern int kl_hash_isfull(hash_t *); | |||||
extern int kl_hash_isempty(hash_t *); | |||||
extern void kl_hash_scan_begin(hscan_t *, hash_t *); | |||||
extern hnode_t *kl_hash_scan_next(hscan_t *); | |||||
extern hnode_t *kl_hash_scan_delete(hash_t *, hnode_t *); | |||||
extern void kl_hash_scan_delfree(hash_t *, hnode_t *); | |||||
extern int kl_hash_verify(hash_t *); | |||||
extern hnode_t *kl_hnode_create(void *); | |||||
extern hnode_t *kl_hnode_init(hnode_t *, void *); | |||||
extern void kl_hnode_destroy(hnode_t *); | |||||
#if defined(HASH_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG) | |||||
#ifdef KAZLIB_SIDEEFFECT_DEBUG | |||||
#define kl_hash_isfull(H) (SFX_CHECK(H)->hash_nodecount == (H)->hash_maxcount) | |||||
#else | |||||
#define kl_hash_isfull(H) ((H)->hash_nodecount == (H)->hash_maxcount) | |||||
#endif | |||||
#define kl_hash_isempty(H) ((H)->hash_nodecount == 0) | |||||
#define kl_hash_count(H) ((H)->hash_nodecount) | |||||
#define kl_hash_size(H) ((H)->hash_nchains) | |||||
#define kl_hnode_get(N) ((N)->hash_data) | |||||
#define kl_hnode_getkey(N) ((N)->hash_key) | |||||
#define kl_hnode_put(N, V) ((N)->hash_data = (V)) | |||||
#endif | |||||
#ifdef __cplusplus | |||||
} | |||||
#endif | |||||
#endif |
@ -0,0 +1,658 @@ | |||||
// This file is part of khash released under the MIT license. | |||||
// See the LICENSE file for more information. | |||||
// Copyright 2013 Cloudant, Inc <support@cloudant.com> | |||||
#include <assert.h> | |||||
#include <string.h> | |||||
#include <stdint.h> | |||||
#include "erl_nif.h" | |||||
#include "hash.h" | |||||
#ifdef _WIN32 | |||||
#define INLINE __inline | |||||
#else | |||||
#define INLINE inline | |||||
#endif | |||||
#define KHASH_VERSION 0 | |||||
typedef struct | |||||
{ | |||||
ERL_NIF_TERM atom_ok; | |||||
ERL_NIF_TERM atom_error; | |||||
ERL_NIF_TERM atom_value; | |||||
ERL_NIF_TERM atom_not_found; | |||||
ERL_NIF_TERM atom_end_of_table; | |||||
ERL_NIF_TERM atom_expired_iterator; | |||||
ErlNifResourceType* res_hash; | |||||
ErlNifResourceType* res_iter; | |||||
} khash_priv; | |||||
typedef struct | |||||
{ | |||||
unsigned int hval; | |||||
ErlNifEnv* env; | |||||
ERL_NIF_TERM key; | |||||
ERL_NIF_TERM val; | |||||
} khnode_t; | |||||
typedef struct | |||||
{ | |||||
int version; | |||||
unsigned int gen; | |||||
hash_t* h; | |||||
ErlNifPid p; | |||||
} khash_t; | |||||
typedef struct | |||||
{ | |||||
int version; | |||||
unsigned int gen; | |||||
khash_t* khash; | |||||
hscan_t scan; | |||||
} khash_iter_t; | |||||
static INLINE ERL_NIF_TERM | |||||
make_atom(ErlNifEnv* env, const char* name) | |||||
{ | |||||
ERL_NIF_TERM ret; | |||||
if(enif_make_existing_atom(env, name, &ret, ERL_NIF_LATIN1)) { | |||||
return ret; | |||||
} | |||||
return enif_make_atom(env, name); | |||||
} | |||||
static INLINE ERL_NIF_TERM | |||||
make_ok(ErlNifEnv* env, khash_priv* priv, ERL_NIF_TERM value) | |||||
{ | |||||
return enif_make_tuple2(env, priv->atom_ok, value); | |||||
} | |||||
static INLINE ERL_NIF_TERM | |||||
make_error(ErlNifEnv* env, khash_priv* priv, ERL_NIF_TERM reason) | |||||
{ | |||||
return enif_make_tuple2(env, priv->atom_error, reason); | |||||
} | |||||
static INLINE int | |||||
check_pid(ErlNifEnv* env, khash_t* khash) | |||||
{ | |||||
ErlNifPid pid; | |||||
enif_self(env, &pid); | |||||
if(enif_compare(pid.pid, khash->p.pid) == 0) { | |||||
return 1; | |||||
} | |||||
return 0; | |||||
} | |||||
hnode_t* | |||||
khnode_alloc(void* ctx) | |||||
{ | |||||
hnode_t* ret = (hnode_t*) enif_alloc(sizeof(hnode_t)); | |||||
khnode_t* node = (khnode_t*) enif_alloc(sizeof(khnode_t)); | |||||
memset(ret, '\0', sizeof(hnode_t)); | |||||
memset(node, '\0', sizeof(khnode_t)); | |||||
node->env = enif_alloc_env(); | |||||
ret->hash_key = node; | |||||
return ret; | |||||
} | |||||
void | |||||
khnode_free(hnode_t* obj, void* ctx) | |||||
{ | |||||
khnode_t* node = (khnode_t*) kl_hnode_getkey(obj); | |||||
enif_free_env(node->env); | |||||
enif_free(node); | |||||
enif_free(obj); | |||||
return; | |||||
} | |||||
int | |||||
khash_cmp_fun(const void* l, const void* r) | |||||
{ | |||||
khnode_t* left = (khnode_t*) l; | |||||
khnode_t* right = (khnode_t*) r; | |||||
int cmp = enif_compare(left->key, right->key); | |||||
if(cmp < 0) { | |||||
return -1; | |||||
} else if(cmp == 0) { | |||||
return 0; | |||||
} else { | |||||
return 1; | |||||
} | |||||
} | |||||
hash_val_t | |||||
khash_hash_fun(const void* obj) | |||||
{ | |||||
khnode_t* node = (khnode_t*) obj; | |||||
return (hash_val_t) node->hval; | |||||
} | |||||
static INLINE khash_t* | |||||
khash_create_int(ErlNifEnv* env, khash_priv* priv, ERL_NIF_TERM opts) | |||||
{ | |||||
khash_t* khash = NULL; | |||||
assert(priv != NULL && "missing private data member"); | |||||
khash = (khash_t*) enif_alloc_resource(priv->res_hash, sizeof(khash_t)); | |||||
memset(khash, '\0', sizeof(khash_t)); | |||||
khash->version = KHASH_VERSION; | |||||
khash->gen = 0; | |||||
khash->h = kl_hash_create(HASHCOUNT_T_MAX, khash_cmp_fun, khash_hash_fun); | |||||
if(khash->h == NULL ) { | |||||
enif_release_resource(khash); | |||||
return NULL; | |||||
} | |||||
kl_hash_set_allocator(khash->h, khnode_alloc, khnode_free, NULL); | |||||
enif_self(env, &(khash->p)); | |||||
return khash; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = khash_create_int(env, priv, argv[0]); | |||||
if(khash == NULL) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ret = enif_make_resource(env, khash); | |||||
enif_release_resource(khash); | |||||
return make_ok(env, priv, ret); | |||||
} | |||||
static void | |||||
khash_free(ErlNifEnv* env, void* obj) | |||||
{ | |||||
khash_t* khash = (khash_t*) obj; | |||||
if(khash->h != NULL) { | |||||
kl_hash_free_nodes(khash->h); | |||||
kl_hash_destroy(khash->h); | |||||
} | |||||
return; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_to_list(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = (khash_priv*) enif_priv_data(env); | |||||
ERL_NIF_TERM ret = enif_make_list(env, 0); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
hscan_t scan; | |||||
hnode_t* entry; | |||||
khnode_t* node; | |||||
ERL_NIF_TERM key; | |||||
ERL_NIF_TERM val; | |||||
ERL_NIF_TERM tuple; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
kl_hash_scan_begin(&scan, khash->h); | |||||
while((entry = kl_hash_scan_next(&scan)) != NULL) { | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
key = enif_make_copy(env, node->key); | |||||
val = enif_make_copy(env, node->val); | |||||
tuple = enif_make_tuple2(env, key, val); | |||||
ret = enif_make_list_cell(env, tuple, ret); | |||||
} | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_clear(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
kl_hash_free_nodes(khash->h); | |||||
khash->gen += 1; | |||||
return priv->atom_ok; | |||||
} | |||||
static INLINE hnode_t* | |||||
khash_lookup_int(ErlNifEnv* env, uint32_t hv, ERL_NIF_TERM key, khash_t* khash) | |||||
{ | |||||
khnode_t node; | |||||
node.hval = hv; | |||||
node.env = env; | |||||
node.key = key; | |||||
return kl_hash_lookup(khash->h, &node); | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_lookup(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
uint32_t hval; | |||||
hnode_t* entry; | |||||
khnode_t* node; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 3) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &hval)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
entry = khash_lookup_int(env, hval, argv[2], khash); | |||||
if(entry == NULL) { | |||||
ret = priv->atom_not_found; | |||||
} else { | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
ret = enif_make_copy(env, node->val); | |||||
ret = enif_make_tuple2(env, priv->atom_value, ret); | |||||
} | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_get(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
uint32_t hval; | |||||
hnode_t* entry; | |||||
khnode_t* node; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 4) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &hval)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
entry = khash_lookup_int(env, hval, argv[2], khash); | |||||
if(entry == NULL) { | |||||
ret = argv[3]; | |||||
} else { | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
ret = enif_make_copy(env, node->val); | |||||
} | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_put(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
uint32_t hval; | |||||
hnode_t* entry; | |||||
khnode_t* node; | |||||
if(argc != 4) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &hval)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
entry = khash_lookup_int(env, hval, argv[2], khash); | |||||
if(entry == NULL) { | |||||
entry = khnode_alloc(NULL); | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
node->hval = hval; | |||||
node->key = enif_make_copy(node->env, argv[2]); | |||||
node->val = enif_make_copy(node->env, argv[3]); | |||||
kl_hash_insert(khash->h, entry, node); | |||||
} else { | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
enif_clear_env(node->env); | |||||
node->key = enif_make_copy(node->env, argv[2]); | |||||
node->val = enif_make_copy(node->env, argv[3]); | |||||
} | |||||
khash->gen += 1; | |||||
return priv->atom_ok; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_del(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
uint32_t hval; | |||||
hnode_t* entry; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 3) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[1], &hval)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
entry = khash_lookup_int(env, hval, argv[2], khash); | |||||
if(entry == NULL) { | |||||
ret = priv->atom_not_found; | |||||
} else { | |||||
kl_hash_delete_free(khash->h, entry); | |||||
ret = priv->atom_ok; | |||||
} | |||||
khash->gen += 1; | |||||
return ret; | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_size(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, (void*) &khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
return enif_make_uint64(env, kl_hash_count(khash->h)); | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_t* khash = NULL; | |||||
void* res = NULL; | |||||
khash_iter_t* iter; | |||||
ERL_NIF_TERM ret; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_hash, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
khash = (khash_t*) res; | |||||
if(!check_pid(env, khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
iter = (khash_iter_t*) enif_alloc_resource( | |||||
priv->res_iter, sizeof(khash_iter_t)); | |||||
memset(iter, '\0', sizeof(khash_iter_t)); | |||||
iter->version = KHASH_VERSION; | |||||
iter->gen = khash->gen; | |||||
iter->khash = khash; | |||||
kl_hash_scan_begin(&(iter->scan), iter->khash->h); | |||||
// The iterator needs to guarantee that the khash | |||||
// remains alive for the life of the iterator. | |||||
enif_keep_resource(khash); | |||||
ret = enif_make_resource(env, iter); | |||||
enif_release_resource(iter); | |||||
return make_ok(env, priv, ret); | |||||
} | |||||
static void | |||||
khash_iter_free(ErlNifEnv* env, void* obj) | |||||
{ | |||||
khash_iter_t* iter = (khash_iter_t*) obj; | |||||
enif_release_resource(iter->khash); | |||||
} | |||||
static ERL_NIF_TERM | |||||
khash_iter_next(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
khash_priv* priv = enif_priv_data(env); | |||||
khash_iter_t* iter = NULL; | |||||
void* res = NULL; | |||||
hnode_t* entry; | |||||
khnode_t* node; | |||||
ERL_NIF_TERM key; | |||||
ERL_NIF_TERM val; | |||||
if(argc != 1) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_resource(env, argv[0], priv->res_iter, &res)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
iter = (khash_iter_t*) res; | |||||
if(!check_pid(env, iter->khash)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(iter->gen != iter->khash->gen) { | |||||
return make_error(env, priv, priv->atom_expired_iterator); | |||||
} | |||||
entry = kl_hash_scan_next(&(iter->scan)); | |||||
if(entry == NULL) { | |||||
return priv->atom_end_of_table; | |||||
} | |||||
node = (khnode_t*) kl_hnode_getkey(entry); | |||||
key = enif_make_copy(env, node->key); | |||||
val = enif_make_copy(env, node->val); | |||||
return enif_make_tuple2(env, key, val); | |||||
} | |||||
static int | |||||
load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) | |||||
{ | |||||
int flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; | |||||
ErlNifResourceType* res; | |||||
khash_priv* new_priv = (khash_priv*) enif_alloc(sizeof(khash_priv)); | |||||
if(new_priv == NULL) { | |||||
return 1; | |||||
} | |||||
res = enif_open_resource_type( | |||||
env, NULL, "khash", khash_free, flags, NULL); | |||||
if(res == NULL) { | |||||
return 1; | |||||
} | |||||
new_priv->res_hash = res; | |||||
res = enif_open_resource_type( | |||||
env, NULL, "khash_iter", khash_iter_free, flags, NULL); | |||||
if(res == NULL) { | |||||
return 1; | |||||
} | |||||
new_priv->res_iter = res; | |||||
new_priv->atom_ok = make_atom(env, "ok"); | |||||
new_priv->atom_error = make_atom(env, "error"); | |||||
new_priv->atom_value = make_atom(env, "value"); | |||||
new_priv->atom_not_found = make_atom(env, "not_found"); | |||||
new_priv->atom_end_of_table = make_atom(env, "end_of_table"); | |||||
new_priv->atom_expired_iterator = make_atom(env, "expired_iterator"); | |||||
*priv = (void*) new_priv; | |||||
return 0; | |||||
} | |||||
static int | |||||
reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info) | |||||
{ | |||||
return 0; | |||||
} | |||||
static int | |||||
upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info) | |||||
{ | |||||
return load(env, priv, info); | |||||
} | |||||
static void | |||||
unload(ErlNifEnv* env, void* priv) | |||||
{ | |||||
enif_free(priv); | |||||
return; | |||||
} | |||||
static ErlNifFunc funcs[] = { | |||||
{"new", 1, khash_new}, | |||||
{"to_list", 1, khash_to_list}, | |||||
{"clear", 1, khash_clear}, | |||||
{"lookup_int", 3, khash_lookup}, | |||||
{"get_int", 4, khash_get}, | |||||
{"put_int", 4, khash_put}, | |||||
{"del_int", 3, khash_del}, | |||||
{"size", 1, khash_size}, | |||||
{"iter", 1, khash_iter}, | |||||
{"iter_next", 1, khash_iter_next} | |||||
}; | |||||
ERL_NIF_INIT(khash, funcs, &load, &reload, &upgrade, &unload); |
@ -0,0 +1,12 @@ | |||||
{port_specs, [ | |||||
{"../../priv/khash2.so", ["*.c"]} | |||||
]}. | |||||
{port_env, [ | |||||
% Development compilation | |||||
% {".*", "CFLAGS", "$CFLAGS -g -Wall -Werror -fPIC"} | |||||
% Production compilation | |||||
{"(linux|solaris|darwin|freebsd)", "CFLAGS", "$CFLAGS -Wall -Werror -DNDEBUG -O3"}, | |||||
{"win32", "CFLAGS", "$CFLAGS /O2 /DNDEBUG /Wall"} | |||||
]}. |
@ -0,0 +1,110 @@ | |||||
#include <stdint.h> | |||||
#include "erl_nif.h" | |||||
#ifdef _WIN32 | |||||
#define INLINE __inline | |||||
#else | |||||
#define INLINE inline | |||||
#endif | |||||
static ErlNifResourceType* ResType = NULL; | |||||
ERL_NIF_TERM null; | |||||
ERL_NIF_TERM ok; | |||||
static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) | |||||
{ | |||||
ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; | |||||
ResType = enif_open_resource_type(env, NULL, "_nifArray_", NULL, flags, NULL); | |||||
if(NULL == ResType) | |||||
return -1; | |||||
null = enif_make_atom(env, "null"); | |||||
ok = enif_make_atom(env, "ok"); | |||||
return 0; | |||||
} | |||||
static ERL_NIF_TERM new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t Size; | |||||
if(!enif_get_uint(env, argv[0], &Size)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ErlNifBinary **ArrayPtr = (ErlNifBinary **) enif_alloc_resource(ResType, sizeof(ErlNifBinary*)*Size); | |||||
int i; | |||||
for(i = 0; i < Size; i++){ | |||||
ArrayPtr[i] = NULL; | |||||
} | |||||
ERL_NIF_TERM Res = enif_make_resource(env, ArrayPtr); | |||||
enif_release_resource(ArrayPtr); | |||||
return Res; | |||||
} | |||||
static ERL_NIF_TERM get(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t Index; | |||||
if(!enif_get_uint(env, argv[1], &Index)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ErlNifBinary **ArrayPtr; | |||||
if (!enif_get_resource(env, argv[0], ResType, (void**)&ArrayPtr)){ | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(NULL != ArrayPtr[Index]){ | |||||
ERL_NIF_TERM Ret; | |||||
enif_binary_to_term(env, ArrayPtr[Index]->data, ArrayPtr[Index]->size, &Ret, 0); | |||||
return Ret; | |||||
} else{ | |||||
return null; | |||||
} | |||||
} | |||||
static ERL_NIF_TERM put(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t Index; | |||||
if(!enif_get_uint(env, argv[1], &Index)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ErlNifBinary *Value = (ErlNifBinary *)malloc(sizeof(ErlNifBinary)); | |||||
if(!enif_term_to_binary(env, argv[2], Value)) | |||||
return enif_make_badarg(env); | |||||
ErlNifBinary **ArrayPtr; | |||||
if (!enif_get_resource(env, argv[0], ResType, (void**)&ArrayPtr)){ | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(NULL == ArrayPtr[Index]){ | |||||
ArrayPtr[Index] = Value; | |||||
return ok; | |||||
} else{ | |||||
enif_release_binary(ArrayPtr[Index]), | |||||
free(ArrayPtr[Index]); | |||||
ArrayPtr[Index] = Value; | |||||
return ok; | |||||
} | |||||
} | |||||
static ERL_NIF_TERM test(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary Value; | |||||
if(!enif_term_to_binary(env, argv[0], &Value)) | |||||
return enif_make_badarg(env); | |||||
// enif_fprintf(stdout, "IMY************ %T", argv[0]); | |||||
ERL_NIF_TERM Ret; | |||||
enif_binary_to_term(env, Value.data, Value.size, &Ret, 0); | |||||
enif_release_binary(&Value); | |||||
return Ret; | |||||
} | |||||
static ErlNifFunc nifFuns[] = { | |||||
{"new", 1, new}, | |||||
{"get", 2, get}, | |||||
{"put", 3, put}, | |||||
{"test", 1, test}, | |||||
}; | |||||
ERL_NIF_INIT(nifArray, nifFuns, &load, NULL, NULL, NULL) |
@ -0,0 +1,108 @@ | |||||
#include <stdint.h> | |||||
#include "erl_nif.h" | |||||
#ifdef _WIN32 | |||||
#define INLINE __inline | |||||
#else | |||||
#define INLINE inline | |||||
#endif | |||||
static ErlNifResourceType* ResType = NULL; | |||||
ERL_NIF_TERM null; | |||||
ERL_NIF_TERM ok; | |||||
static int load(ErlNifEnv* env, void** priv, ERL_NIF_TERM load_info) | |||||
{ | |||||
ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; | |||||
ResType = enif_open_resource_type(env, NULL, "_nifArray_", NULL, flags, NULL); | |||||
if(NULL == ResType) | |||||
return -1; | |||||
null = enif_make_atom(env, "null"); | |||||
ok = enif_make_atom(env, "ok"); | |||||
return 0; | |||||
} | |||||
static ERL_NIF_TERM new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t Size; | |||||
if(!enif_get_uint(env, argv[0], &Size)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ErlNifBinary *ArrayPtr = (ErlNifBinary *) enif_alloc_resource(ResType, sizeof(ErlNifBinary)*Size); | |||||
int i; | |||||
for(i = 0; i < Size; i++){ | |||||
ErlNifBinary nullBin; | |||||
enif_term_to_binary(env, null, &nullBin); | |||||
ArrayPtr[i] = nullBin; | |||||
} | |||||
ERL_NIF_TERM Res = enif_make_resource(env, ArrayPtr); | |||||
enif_release_resource(ArrayPtr); | |||||
return Res; | |||||
} | |||||
static ERL_NIF_TERM get(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
uint32_t Index; | |||||
if(!enif_get_uint(env, argv[1], &Index)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
ErlNifBinary *ArrayPtr; | |||||
if (!enif_get_resource(env, argv[0], ResType, (void **)&ArrayPtr)){ | |||||
return enif_make_badarg(env); | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get %T \n", argv[1]); | |||||
ERL_NIF_TERM Ret; | |||||
enif_binary_to_term(env, ArrayPtr[Index].data, ArrayPtr[Index].size, &Ret, 0); | |||||
return Ret; | |||||
} | |||||
static ERL_NIF_TERM put(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
//enif_fprintf(stdout, "IMY************put0001 %T \n", argv[1]); | |||||
uint32_t Index; | |||||
if(!enif_get_uint(env, argv[1], &Index)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
//enif_fprintf(stdout, "IMY************put0002 %T %T \n", argv[1], argv[2]); | |||||
ErlNifBinary Value; | |||||
if(!enif_term_to_binary(env, argv[2], &Value)) | |||||
return enif_make_badarg(env); | |||||
ErlNifBinary *ArrayPtr; | |||||
//enif_fprintf(stdout, "IMY************put0003 %T \n", argv[1]); | |||||
if (!enif_get_resource(env, argv[0], ResType, (void **)&ArrayPtr)){ | |||||
return enif_make_badarg(env); | |||||
} | |||||
//enif_fprintf(stdout, "IMY************put111 %T \n", argv[1]); | |||||
ErlNifBinary OldValue = ArrayPtr[Index]; | |||||
// enif_fprintf(stdout, "IMY************put222 %T %d \n", argv[1], Index); | |||||
ArrayPtr[Index] = Value; | |||||
//enif_fprintf(stdout, "IMY************put333 %T \n", argv[1]); | |||||
enif_release_binary(&OldValue); | |||||
//enif_fprintf(stdout, "IMY************put444 %T \n", argv[1]); | |||||
return ok; | |||||
} | |||||
static ERL_NIF_TERM test(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||||
{ | |||||
ErlNifBinary Value; | |||||
if(!enif_term_to_binary(env, argv[0], &Value)) | |||||
return enif_make_badarg(env); | |||||
// enif_fprintf(stdout, "IMY************ %T", argv[0]); | |||||
ERL_NIF_TERM Ret; | |||||
enif_binary_to_term(env, Value.data, Value.size, &Ret, 0); | |||||
enif_release_binary(&Value); | |||||
return Ret; | |||||
} | |||||
static ErlNifFunc nifFuns[] = { | |||||
{"new", 1, new}, | |||||
{"get", 2, get}, | |||||
{"put", 3, put}, | |||||
{"test", 1, test}, | |||||
}; | |||||
ERL_NIF_INIT(nifArray, nifFuns, &load, NULL, NULL, NULL) |
@ -0,0 +1,7 @@ | |||||
{port_specs, [ | |||||
{"../../priv/nifArray.so", ["*.c"]} | |||||
]}. | |||||
@ -0,0 +1,455 @@ | |||||
#include <stdint.h> | |||||
#include <string.h> | |||||
#include "erl_nif.h" | |||||
typedef struct { | |||||
size_t keySize; | |||||
size_t bblSize_1; | |||||
size_t bblSize_2; | |||||
struct _bbl **arrayPtr; | |||||
} hashb; | |||||
typedef struct _bbl { | |||||
struct _node *head; | |||||
} bbl; | |||||
typedef struct _node { | |||||
struct _node *next; | |||||
ErlNifBinary Key; | |||||
ErlNifBinary Value; | |||||
} node; | |||||
#define BBLSIZE_1 128 | |||||
#define BBLSIZE_2 128 | |||||
static ErlNifResourceType *ResType = NULL; | |||||
static ERL_NIF_TERM undefined; | |||||
static ERL_NIF_TERM ok; | |||||
///////////////////////////////hash 函数 ///////////////////////////// | |||||
static uint32_t fnv_hash(const unsigned char *data, size_t size, uint32_t salt) { | |||||
uint32_t i; | |||||
uint64_t hashv; | |||||
const unsigned char *_hf_key = (const unsigned char *) (data); | |||||
hashv = 2166136261; | |||||
for (i = 0; i < size; i++) { | |||||
hashv = hashv ^ _hf_key[i]; | |||||
hashv = hashv * 16777619; | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get %T \n", argv[1]); | |||||
return hashv; | |||||
} | |||||
static uint32_t murmurhash(const unsigned char *key, uint32_t len, uint32_t seed) { | |||||
//uint32_t c1 = 0xcc9e2d51; | |||||
//uint32_t c2 = 0x1b873593; | |||||
//uint32_t r1 = 15; | |||||
//uint32_t r2 = 13; | |||||
//uint32_t m = 5; | |||||
//uint32_t n = 0xe6546b64; | |||||
uint32_t h = 0; | |||||
uint32_t k = 0; | |||||
uint8_t *d = (uint8_t *) key; // 32 bit extract from `key' | |||||
const uint32_t *chunks = NULL; | |||||
const uint8_t *tail = NULL; // tail - last 8 bytes | |||||
int i = 0; | |||||
int l = len / 4; // chunk length | |||||
h = seed; | |||||
chunks = (const uint32_t *) (d + l * 4); // body | |||||
tail = (const uint8_t *) (d + l * 4); // last 8 byte chunk of `key' | |||||
// for each 4 byte chunk of `key' | |||||
for (i = -l; i != 0; ++i) { | |||||
// next 4 byte chunk of `key' | |||||
k = chunks[i]; | |||||
// encode next 4 byte chunk of `key' | |||||
k *= 0xcc9e2d51; | |||||
k = (k << 15) | (k >> (32 - 15)); | |||||
k *= 0x1b873593; | |||||
// append to hash | |||||
h ^= k; | |||||
h = (h << 13) | (h >> (32 - 13)); | |||||
h = h * 5 + 0xe6546b64; | |||||
} | |||||
k = 0; | |||||
// remainder | |||||
switch (len & 3) { // `len % 4' | |||||
case 3: | |||||
k ^= (tail[2] << 16); | |||||
case 2: | |||||
k ^= (tail[1] << 8); | |||||
case 1: | |||||
k ^= tail[0]; | |||||
k *= 0xcc9e2d51; | |||||
k = (k << 15) | (k >> (32 - 15)); | |||||
k *= 0x1b873593; | |||||
h ^= k; | |||||
} | |||||
h ^= len; | |||||
h ^= (h >> 16); | |||||
h *= 0x85ebca6b; | |||||
h ^= (h >> 13); | |||||
h *= 0xc2b2ae35; | |||||
h ^= (h >> 16); | |||||
return h; | |||||
} | |||||
//////////////////////////////////hash 函数测试/////////////////////////// | |||||
static ERL_NIF_TERM hash1(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
ErlNifBinary Value; | |||||
if (!enif_term_to_binary(env, argv[0], &Value)) | |||||
return enif_make_badarg(env); | |||||
uint32_t Salt; | |||||
if (!enif_get_uint(env, argv[1], &Salt)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
Salt = fnv_hash(Value.data, Value.size, Salt); | |||||
enif_release_binary(&Value); | |||||
return ok; | |||||
}; | |||||
static ERL_NIF_TERM hash2(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
ErlNifUInt64 Salt; | |||||
if (!enif_get_uint64(env, argv[1], &Salt)) | |||||
return enif_make_badarg(env); | |||||
ErlNifBinary Value; | |||||
if (!enif_term_to_binary(env, argv[0], &Value)) | |||||
return enif_make_badarg(env); | |||||
enif_release_binary(&Value); | |||||
Salt = enif_hash(ERL_NIF_INTERNAL_HASH, argv[1], 2423432432); | |||||
return ok; | |||||
}; | |||||
static ERL_NIF_TERM hash3(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
ErlNifUInt64 Salt; | |||||
if (!enif_get_uint64(env, argv[1], &Salt)) | |||||
return enif_make_badarg(env); | |||||
ErlNifBinary Value; | |||||
if (!enif_term_to_binary(env, argv[0], &Value)) | |||||
return enif_make_badarg(env); | |||||
Salt = murmurhash(Value.data, Value.size, 0); | |||||
enif_release_binary(&Value); | |||||
return ok; | |||||
}; | |||||
static uint8_t compareChar1(const unsigned char *src, const unsigned char *dst, size_t Size) { | |||||
// int j = 0; | |||||
// unsigned char *tsrc, *tdst; | |||||
// tsrc = src; | |||||
// tdst = dst; | |||||
// for(j = 0; j <= Size; j++) | |||||
// { | |||||
// enif_fprintf(stdout, "IMY************get9999111 src %d dst %d \n", *tsrc, *tdst); | |||||
// tsrc++; | |||||
// tdst++; | |||||
// } | |||||
uint8_t i = 0; | |||||
while (i < Size-1 && (*src == *dst)) { | |||||
// enif_fprintf(stdout, "IMY************get99992222 %d %d \n", *src, *dst); | |||||
src++; | |||||
dst++; | |||||
i++; | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get9999while end %d %d \n", *src, *dst); | |||||
if (*src != *dst) { | |||||
//enif_fprintf(stdout, "IMY************get99993333 %d %d \n", *src, *dst); | |||||
return 1; | |||||
} else { | |||||
//enif_fprintf(stdout, "IMY************get99995555 %d %d \n", *src, *dst); | |||||
return 0; | |||||
} | |||||
} | |||||
static uint8_t compareChar2(const unsigned char *src, const unsigned char *dst, size_t Size) { | |||||
const uint32_t *intSrc = NULL; | |||||
const uint32_t *intDst = NULL; | |||||
const uint8_t *tailSrc = NULL; // tail - last 8 bytes | |||||
const uint8_t *tailDst = NULL; // tail - last 8 bytes | |||||
int l = Size / 4; // chunk length | |||||
intSrc = (const uint32_t *) src; // body | |||||
intDst = (const uint32_t *) dst; // body | |||||
tailSrc = (const uint8_t *) (src + l * 4); // last 8 byte chunk of `key' | |||||
tailDst = (const uint8_t *) (dst + l * 4); // last 8 byte chunk of `key' | |||||
// for each 4 byte chunk of `key' | |||||
int i = 0; | |||||
for (i = 0; i < l; i++) { | |||||
if (intSrc[i] != intDst[i]) | |||||
return 1; | |||||
} | |||||
// remainder | |||||
switch (Size & 3) { | |||||
case 3: | |||||
if (tailSrc[0] != tailDst[0] || tailSrc[1] != tailDst[1] || tailSrc[2] != tailDst[2]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
case 2: | |||||
if (tailSrc[0] != tailDst[0] || tailSrc[1] != tailDst[1]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
case 1: | |||||
if (tailSrc[0] != tailDst[0]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
} | |||||
return 0; | |||||
} | |||||
static ERL_NIF_TERM compareBin1(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
ErlNifBinary src, dst; | |||||
if (!(enif_inspect_binary(env, argv[0], &src) && enif_inspect_binary(env, argv[1], &dst))) | |||||
return enif_make_badarg(env); | |||||
if (src.size != dst.size) { | |||||
return enif_make_int(env, 1); | |||||
} | |||||
int Ret = compareChar1(src.data, dst.data, src.size); | |||||
return enif_make_int(env, Ret); | |||||
} | |||||
static ERL_NIF_TERM compareBin2(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
ErlNifBinary src, dst; | |||||
if (!(enif_inspect_binary(env, argv[0], &src) && enif_inspect_binary(env, argv[1], &dst))) | |||||
return enif_make_badarg(env); | |||||
if (src.size != dst.size) { | |||||
return enif_make_int(env, 1); | |||||
} | |||||
int Ret = compareChar2(src.data, dst.data, src.size); | |||||
return enif_make_int(env, Ret); | |||||
} | |||||
///////////////////////////////////////////////////////////////////// | |||||
static int load(ErlNifEnv *env, void **priv, ERL_NIF_TERM load_info) { | |||||
ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; | |||||
ResType = enif_open_resource_type(env, NULL, "_nifHashb_", NULL, flags, NULL); | |||||
if (NULL == ResType) | |||||
return -1; | |||||
undefined = enif_make_atom(env, "undefined"); | |||||
ok = enif_make_atom(env, "ok"); | |||||
return 0; | |||||
} | |||||
static ERL_NIF_TERM new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
hashb *ResPtr = (hashb *) enif_alloc_resource(ResType, sizeof(hashb)); | |||||
ResPtr->keySize = 0; | |||||
ResPtr->bblSize_1 = BBLSIZE_1; | |||||
ResPtr->bblSize_2 = BBLSIZE_2; | |||||
ResPtr->arrayPtr = enif_alloc(sizeof(bbl *) * BBLSIZE_1); | |||||
int i, j; | |||||
bbl NullBbl; | |||||
NullBbl.head = NULL; | |||||
for (i = 0; i < BBLSIZE_1; i++) { | |||||
ResPtr->arrayPtr[i] = enif_alloc(sizeof(bbl) * BBLSIZE_2); | |||||
for (j = 0; j < BBLSIZE_2; j++) { | |||||
ResPtr->arrayPtr[i][j] = NullBbl; | |||||
} | |||||
} | |||||
ERL_NIF_TERM Res = enif_make_resource(env, ResPtr); | |||||
enif_release_resource(ResPtr); | |||||
return Res; | |||||
} | |||||
static uint8_t compareChar(const unsigned char *src, const unsigned char *dst, size_t Size) { | |||||
const uint32_t *intSrc = NULL; | |||||
const uint32_t *intDst = NULL; | |||||
const uint8_t *tailSrc = NULL; // tail - last 8 bytes | |||||
const uint8_t *tailDst = NULL; // tail - last 8 bytes | |||||
int l = Size / 4; // chunk length | |||||
intSrc = (const uint32_t *) src; // body | |||||
intDst = (const uint32_t *) dst; // body | |||||
tailSrc = (const uint8_t *) (src + l * 4); // last 8 byte chunk of `key' | |||||
tailDst = (const uint8_t *) (dst + l * 4); // last 8 byte chunk of `key' | |||||
// for each 4 byte chunk of `key' | |||||
int i = 0; | |||||
for (i = 0; i < l; i++) { | |||||
if (intSrc[i] != intDst[i]) | |||||
return 1; | |||||
} | |||||
// remainder | |||||
switch (Size & 3) { | |||||
case 3: | |||||
if (tailSrc[0] != tailDst[0] || tailSrc[1] != tailDst[1] || tailSrc[2] != tailDst[2]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
case 2: | |||||
if (tailSrc[0] != tailDst[0] || tailSrc[1] != tailDst[1]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
case 1: | |||||
if (tailSrc[0] != tailDst[0]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
} | |||||
return 0; | |||||
} | |||||
static uint8_t compareBin(ErlNifBinary src, ErlNifBinary dst) { | |||||
if (src.size != dst.size) { | |||||
//enif_fprintf(stdout, "IMY************get8888\n"); | |||||
return 1; | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get8888111 %d \n", src.size); | |||||
return compareChar(src.data, dst.data, src.size); | |||||
} | |||||
static ERL_NIF_TERM get(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
hashb *ResPtr; | |||||
if (!enif_get_resource(env, argv[0], ResType, (void **) &ResPtr)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get1111\n"); | |||||
uint32_t BblIndex_1, BblIndex_2; | |||||
if(!enif_get_uint(env, argv[1], &BblIndex_1)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get22221111\n"); | |||||
if(!enif_get_uint(env, argv[2], &BblIndex_2)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get2222\n"); | |||||
BblIndex_1 = BblIndex_1 % ResPtr->bblSize_1; | |||||
BblIndex_2 = BblIndex_2 % ResPtr->bblSize_2; | |||||
//enif_fprintf(stdout, "IMY************get3333\n"); | |||||
if (NULL == ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head) { | |||||
//enif_fprintf(stdout, "IMY************get4444\n"); | |||||
return undefined; | |||||
} else { | |||||
//enif_fprintf(stdout, "IMY************get55555\n"); | |||||
ErlNifBinary getKey; | |||||
if (!enif_inspect_binary(env, argv[3], &getKey)) | |||||
return enif_make_badarg(env); | |||||
node *Head = ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head; | |||||
while (Head) { | |||||
//enif_fprintf(stdout, "IMY************get7777\n"); | |||||
if (compareBin(getKey, Head->Key) == 0) { | |||||
ERL_NIF_TERM Ret; | |||||
enif_binary_to_term(env, Head->Value.data, Head->Value.size, &Ret, 0); | |||||
//enif_fprintf(stdout, "IMY************get5555\n"); | |||||
return Ret; | |||||
} | |||||
Head = Head->next; | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get6666\n"); | |||||
return undefined; | |||||
} | |||||
} | |||||
static ERL_NIF_TERM put(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
hashb *ResPtr; | |||||
if (!enif_get_resource(env, argv[0], ResType, (void **) &ResPtr)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
//enif_fprintf(stdout, "IMY************put111 \n"); | |||||
uint32_t BblIndex_1, BblIndex_2; | |||||
if(!enif_get_uint(env, argv[1], &BblIndex_1)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
if(!enif_get_uint(env, argv[2], &BblIndex_2)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
BblIndex_1 = BblIndex_1 % ResPtr->bblSize_1; | |||||
BblIndex_2 = BblIndex_2 % ResPtr->bblSize_2; | |||||
ErlNifBinary Key, Value; | |||||
if (!enif_inspect_binary(env, argv[3], &Key)) | |||||
return enif_make_badarg(env); | |||||
if (!enif_inspect_binary(env, argv[4], &Value)) | |||||
return enif_make_badarg(env); | |||||
//enif_fprintf(stdout, "IMY************put222 %d %d \n", BblIndex_1, BblIndex_2); | |||||
//enif_fprintf(stdout, "IMY************put3333 \n"); | |||||
if (NULL == ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head) { | |||||
node *NewNode = enif_alloc(sizeof(node)); | |||||
enif_alloc_binary(Key.size, &NewNode->Key); | |||||
enif_alloc_binary(Value.size, &NewNode->Value); | |||||
memcpy(NewNode->Key.data, Key.data, Key.size); | |||||
memcpy(NewNode->Value.data, Value.data, Value.size); | |||||
NewNode->next = NULL; | |||||
ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head = NewNode; | |||||
ResPtr->keySize = ResPtr->keySize + 1; | |||||
//enif_fprintf(stdout, "IMY************put4444 \n"); | |||||
return ok; | |||||
} else { | |||||
node *Head = ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head; | |||||
while (Head) { | |||||
if (compareBin(Key, Head->Key) == 0) { | |||||
//ERL_NIF_TERM Ret; | |||||
ErlNifBinary OldValue = Head->Value; | |||||
enif_release_binary(&OldValue); | |||||
//enif_binary_to_term(env, OldValue.data, OldValue.size, &Ret, 0); | |||||
ErlNifBinary NewValue; | |||||
enif_alloc_binary(Value.size, &NewValue); | |||||
memcpy(NewValue.data, Value.data, Value.size); | |||||
Head->Value = NewValue; | |||||
//enif_fprintf(stdout, "IMY************put55555 \n"); | |||||
//return Ret; | |||||
return ok; | |||||
} | |||||
Head = Head->next; | |||||
} | |||||
node *NewNode = enif_alloc(sizeof(node)); | |||||
enif_alloc_binary(Key.size, &NewNode->Key); | |||||
enif_alloc_binary(Value.size, &NewNode->Value); | |||||
memcpy(NewNode->Key.data, Key.data, Key.size); | |||||
memcpy(NewNode->Value.data, Value.data, Value.size); | |||||
NewNode->next = ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head; | |||||
ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head = NewNode; | |||||
ResPtr->keySize = ResPtr->keySize + 1; | |||||
//enif_fprintf(stdout, "IMY************put6666\n"); | |||||
return ok; | |||||
} | |||||
} | |||||
///////////////////////////////////////////////////////////////////////// | |||||
static ErlNifFunc nifFuns[] = { | |||||
{"new", 0, new}, | |||||
{"get", 4, get}, | |||||
{"put", 5, put}, | |||||
{"hash1", 2, hash1}, | |||||
{"hash2", 2, hash2}, | |||||
{"hash3", 2, hash3}, | |||||
{"compareBin1", 2, compareBin1}, | |||||
{"compareBin2", 2, compareBin2}, | |||||
}; | |||||
ERL_NIF_INIT(nifHashb, nifFuns, | |||||
&load, NULL, NULL, NULL) | |||||
@ -0,0 +1,435 @@ | |||||
#include <stdint.h> | |||||
#include <string.h> | |||||
#include "erl_nif.h" | |||||
typedef struct { | |||||
size_t keySize; | |||||
size_t bblSize_1; | |||||
size_t bblSize_2; | |||||
struct _bbl **arrayPtr; | |||||
} hashb; | |||||
typedef struct _bbl { | |||||
struct _node *head; | |||||
} bbl; | |||||
typedef struct _node { | |||||
struct _node *next; | |||||
ErlNifBinary Key; | |||||
ErlNifBinary Value; | |||||
} node; | |||||
#define BBLSIZE_1 128 | |||||
#define BBLSIZE_2 128 | |||||
static ErlNifResourceType *ResType = NULL; | |||||
static ERL_NIF_TERM undefined; | |||||
static ERL_NIF_TERM ok; | |||||
///////////////////////////////hash 函数 ///////////////////////////// | |||||
static uint32_t fnv_hash(const unsigned char *data, size_t size, uint32_t salt) { | |||||
uint32_t i; | |||||
uint64_t hashv; | |||||
const unsigned char *_hf_key = (const unsigned char *) (data); | |||||
hashv = 2166136261; | |||||
for (i = 0; i < size; i++) { | |||||
hashv = hashv ^ _hf_key[i]; | |||||
hashv = hashv * 16777619; | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get %T \n", argv[1]); | |||||
return hashv; | |||||
} | |||||
static uint32_t murmurhash(const unsigned char *key, uint32_t len, uint32_t seed) { | |||||
//uint32_t c1 = 0xcc9e2d51; | |||||
//uint32_t c2 = 0x1b873593; | |||||
//uint32_t r1 = 15; | |||||
//uint32_t r2 = 13; | |||||
//uint32_t m = 5; | |||||
//uint32_t n = 0xe6546b64; | |||||
uint32_t h = 0; | |||||
uint32_t k = 0; | |||||
uint8_t *d = (uint8_t *) key; // 32 bit extract from `key' | |||||
const uint32_t *chunks = NULL; | |||||
const uint8_t *tail = NULL; // tail - last 8 bytes | |||||
int i = 0; | |||||
int l = len / 4; // chunk length | |||||
h = seed; | |||||
chunks = (const uint32_t *) (d + l * 4); // body | |||||
tail = (const uint8_t *) (d + l * 4); // last 8 byte chunk of `key' | |||||
// for each 4 byte chunk of `key' | |||||
for (i = -l; i != 0; ++i) { | |||||
// next 4 byte chunk of `key' | |||||
k = chunks[i]; | |||||
// encode next 4 byte chunk of `key' | |||||
k *= 0xcc9e2d51; | |||||
k = (k << 15) | (k >> (32 - 15)); | |||||
k *= 0x1b873593; | |||||
// append to hash | |||||
h ^= k; | |||||
h = (h << 13) | (h >> (32 - 13)); | |||||
h = h * 5 + 0xe6546b64; | |||||
} | |||||
k = 0; | |||||
// remainder | |||||
switch (len & 3) { // `len % 4' | |||||
case 3: | |||||
k ^= (tail[2] << 16); | |||||
case 2: | |||||
k ^= (tail[1] << 8); | |||||
case 1: | |||||
k ^= tail[0]; | |||||
k *= 0xcc9e2d51; | |||||
k = (k << 15) | (k >> (32 - 15)); | |||||
k *= 0x1b873593; | |||||
h ^= k; | |||||
} | |||||
h ^= len; | |||||
h ^= (h >> 16); | |||||
h *= 0x85ebca6b; | |||||
h ^= (h >> 13); | |||||
h *= 0xc2b2ae35; | |||||
h ^= (h >> 16); | |||||
return h; | |||||
} | |||||
//////////////////////////////////hash 函数测试/////////////////////////// | |||||
static ERL_NIF_TERM hash1(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
ErlNifBinary Value; | |||||
if (!enif_term_to_binary(env, argv[0], &Value)) | |||||
return enif_make_badarg(env); | |||||
uint32_t Salt; | |||||
if (!enif_get_uint(env, argv[1], &Salt)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
Salt = fnv_hash(Value.data, Value.size, Salt); | |||||
enif_release_binary(&Value); | |||||
return ok; | |||||
}; | |||||
static ERL_NIF_TERM hash2(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
ErlNifUInt64 Salt; | |||||
if (!enif_get_uint64(env, argv[1], &Salt)) | |||||
return enif_make_badarg(env); | |||||
ErlNifBinary Value; | |||||
if (!enif_term_to_binary(env, argv[0], &Value)) | |||||
return enif_make_badarg(env); | |||||
enif_release_binary(&Value); | |||||
Salt = enif_hash(ERL_NIF_INTERNAL_HASH, argv[1], 2423432432); | |||||
return ok; | |||||
}; | |||||
static ERL_NIF_TERM hash3(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
ErlNifUInt64 Salt; | |||||
if (!enif_get_uint64(env, argv[1], &Salt)) | |||||
return enif_make_badarg(env); | |||||
ErlNifBinary Value; | |||||
if (!enif_term_to_binary(env, argv[0], &Value)) | |||||
return enif_make_badarg(env); | |||||
Salt = murmurhash(Value.data, Value.size, 0); | |||||
enif_release_binary(&Value); | |||||
return ok; | |||||
}; | |||||
static uint8_t compareChar1(const unsigned char *src, const unsigned char *dst, size_t Size) { | |||||
// int j = 0; | |||||
// unsigned char *tsrc, *tdst; | |||||
// tsrc = src; | |||||
// tdst = dst; | |||||
// for(j = 0; j <= Size; j++) | |||||
// { | |||||
// enif_fprintf(stdout, "IMY************get9999111 src %d dst %d \n", *tsrc, *tdst); | |||||
// tsrc++; | |||||
// tdst++; | |||||
// } | |||||
uint8_t i = 0; | |||||
while (i < Size-1 && (*src == *dst)) { | |||||
// enif_fprintf(stdout, "IMY************get99992222 %d %d \n", *src, *dst); | |||||
src++; | |||||
dst++; | |||||
i++; | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get9999while end %d %d \n", *src, *dst); | |||||
if (*src != *dst) { | |||||
//enif_fprintf(stdout, "IMY************get99993333 %d %d \n", *src, *dst); | |||||
return 1; | |||||
} else { | |||||
//enif_fprintf(stdout, "IMY************get99995555 %d %d \n", *src, *dst); | |||||
return 0; | |||||
} | |||||
} | |||||
static uint8_t compareChar2(const unsigned char *src, const unsigned char *dst, size_t Size) { | |||||
const uint32_t *intSrc = NULL; | |||||
const uint32_t *intDst = NULL; | |||||
const uint8_t *tailSrc = NULL; // tail - last 8 bytes | |||||
const uint8_t *tailDst = NULL; // tail - last 8 bytes | |||||
int l = Size / 4; // chunk length | |||||
intSrc = (const uint32_t *) src; // body | |||||
intDst = (const uint32_t *) dst; // body | |||||
tailSrc = (const uint8_t *) (src + l * 4); // last 8 byte chunk of `key' | |||||
tailDst = (const uint8_t *) (dst + l * 4); // last 8 byte chunk of `key' | |||||
// for each 4 byte chunk of `key' | |||||
int i = 0; | |||||
for (i = 0; i < l; i++) { | |||||
if (intSrc[i] != intDst[i]) | |||||
return 1; | |||||
} | |||||
// remainder | |||||
switch (Size & 3) { | |||||
case 3: | |||||
if (tailSrc[0] != tailDst[0] || tailSrc[1] != tailDst[1] || tailSrc[2] != tailDst[2]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
case 2: | |||||
if (tailSrc[0] != tailDst[0] || tailSrc[1] != tailDst[1]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
case 1: | |||||
if (tailSrc[0] != tailDst[0]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
} | |||||
return 0; | |||||
} | |||||
static ERL_NIF_TERM compareBin1(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
ErlNifBinary src, dst; | |||||
if (!(enif_inspect_binary(env, argv[0], &src) && enif_inspect_binary(env, argv[1], &dst))) | |||||
return enif_make_badarg(env); | |||||
if (src.size != dst.size) { | |||||
return enif_make_int(env, 1); | |||||
} | |||||
int Ret = compareChar1(src.data, dst.data, src.size); | |||||
return enif_make_int(env, Ret); | |||||
} | |||||
static ERL_NIF_TERM compareBin2(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
ErlNifBinary src, dst; | |||||
if (!(enif_inspect_binary(env, argv[0], &src) && enif_inspect_binary(env, argv[1], &dst))) | |||||
return enif_make_badarg(env); | |||||
if (src.size != dst.size) { | |||||
return enif_make_int(env, 1); | |||||
} | |||||
int Ret = compareChar2(src.data, dst.data, src.size); | |||||
return enif_make_int(env, Ret); | |||||
} | |||||
///////////////////////////////////////////////////////////////////// | |||||
static int load(ErlNifEnv *env, void **priv, ERL_NIF_TERM load_info) { | |||||
ErlNifResourceFlags flags = ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER; | |||||
ResType = enif_open_resource_type(env, NULL, "_nifHashb_", NULL, flags, NULL); | |||||
if (NULL == ResType) | |||||
return -1; | |||||
undefined = enif_make_atom(env, "undefined"); | |||||
ok = enif_make_atom(env, "ok"); | |||||
return 0; | |||||
} | |||||
static ERL_NIF_TERM new(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
hashb *ResPtr = (hashb *) enif_alloc_resource(ResType, sizeof(hashb)); | |||||
ResPtr->keySize = 0; | |||||
ResPtr->bblSize_1 = BBLSIZE_1; | |||||
ResPtr->bblSize_2 = BBLSIZE_2; | |||||
ResPtr->arrayPtr = enif_alloc(sizeof(bbl *) * BBLSIZE_1); | |||||
int i, j; | |||||
bbl NullBbl; | |||||
NullBbl.head = NULL; | |||||
for (i = 0; i < BBLSIZE_1; i++) { | |||||
ResPtr->arrayPtr[i] = enif_alloc(sizeof(bbl) * BBLSIZE_2); | |||||
for (j = 0; j < BBLSIZE_2; j++) { | |||||
ResPtr->arrayPtr[i][j] = NullBbl; | |||||
} | |||||
} | |||||
ERL_NIF_TERM Res = enif_make_resource(env, ResPtr); | |||||
enif_release_resource(ResPtr); | |||||
return Res; | |||||
} | |||||
static uint8_t compareChar(const unsigned char *src, const unsigned char *dst, size_t Size) { | |||||
const uint32_t *intSrc = NULL; | |||||
const uint32_t *intDst = NULL; | |||||
const uint8_t *tailSrc = NULL; // tail - last 8 bytes | |||||
const uint8_t *tailDst = NULL; // tail - last 8 bytes | |||||
int l = Size / 4; // chunk length | |||||
intSrc = (const uint32_t *) src; // body | |||||
intDst = (const uint32_t *) dst; // body | |||||
tailSrc = (const uint8_t *) (src + l * 4); // last 8 byte chunk of `key' | |||||
tailDst = (const uint8_t *) (dst + l * 4); // last 8 byte chunk of `key' | |||||
// for each 4 byte chunk of `key' | |||||
int i = 0; | |||||
for (i = 0; i < l; i++) { | |||||
if (intSrc[i] != intDst[i]) | |||||
return 1; | |||||
} | |||||
// remainder | |||||
switch (Size & 3) { | |||||
case 3: | |||||
if (tailSrc[0] != tailDst[0] || tailSrc[1] != tailDst[1] || tailSrc[2] != tailDst[2]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
case 2: | |||||
if (tailSrc[0] != tailDst[0] || tailSrc[1] != tailDst[1]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
case 1: | |||||
if (tailSrc[0] != tailDst[0]) { | |||||
return 1; | |||||
} | |||||
break; | |||||
} | |||||
return 0; | |||||
} | |||||
static uint8_t compareBin(ErlNifBinary src, ErlNifBinary dst) { | |||||
if (src.size != dst.size) { | |||||
//enif_fprintf(stdout, "IMY************get8888\n"); | |||||
return 1; | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get8888111 %d \n", src.size); | |||||
return compareChar(src.data, dst.data, src.size); | |||||
} | |||||
static ERL_NIF_TERM get(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
hashb *ResPtr; | |||||
if (!enif_get_resource(env, argv[0], ResType, (void **) &ResPtr)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get1111\n"); | |||||
ErlNifBinary getKey; | |||||
if (!enif_term_to_binary(env, argv[1], &getKey)) | |||||
return enif_make_badarg(env); | |||||
//enif_fprintf(stdout, "IMY************get2222\n"); | |||||
uint32_t BblIndex_1, BblIndex_2; | |||||
BblIndex_1 = murmurhash(getKey.data, getKey.size, 131) % ResPtr->bblSize_1; | |||||
BblIndex_2 = murmurhash(getKey.data, getKey.size, 16777619) % ResPtr->bblSize_2; | |||||
//enif_fprintf(stdout, "IMY************get3333\n"); | |||||
if (NULL == ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head) { | |||||
//enif_fprintf(stdout, "IMY************get4444\n"); | |||||
enif_release_binary(&getKey); | |||||
return undefined; | |||||
} else { | |||||
// enif_fprintf(stdout, "IMY************get55555\n"); | |||||
node *Head = ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head; | |||||
while (Head) { | |||||
// enif_fprintf(stdout, "IMY************get7777\n"); | |||||
if (compareBin(getKey, Head->Key) == 0) { | |||||
ERL_NIF_TERM Ret; | |||||
enif_binary_to_term(env, Head->Value.data, Head->Value.size, &Ret, 0); | |||||
//enif_fprintf(stdout, "IMY************get5555\n"); | |||||
enif_release_binary(&getKey); | |||||
return Ret; | |||||
} | |||||
Head = Head->next; | |||||
} | |||||
//enif_fprintf(stdout, "IMY************get6666\n"); | |||||
enif_release_binary(&getKey); | |||||
return undefined; | |||||
} | |||||
} | |||||
static ERL_NIF_TERM put(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { | |||||
hashb *ResPtr; | |||||
if (!enif_get_resource(env, argv[0], ResType, (void **) &ResPtr)) { | |||||
return enif_make_badarg(env); | |||||
} | |||||
//enif_fprintf(stdout, "IMY************put111 \n"); | |||||
ErlNifBinary Key, Value; | |||||
if (!enif_term_to_binary(env, argv[1], &Key)) | |||||
return enif_make_badarg(env); | |||||
if (!enif_term_to_binary(env, argv[2], &Value)) | |||||
return enif_make_badarg(env); | |||||
uint32_t BblIndex_1, BblIndex_2; | |||||
BblIndex_1 = murmurhash(Key.data, Key.size, 131) % ResPtr->bblSize_1; | |||||
BblIndex_2 = murmurhash(Key.data, Key.size, 16777619) % ResPtr->bblSize_2; | |||||
//enif_fprintf(stdout, "IMY************put222 %d %d \n", BblIndex_1, BblIndex_2); | |||||
//enif_fprintf(stdout, "IMY************put3333 \n"); | |||||
if (NULL == ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head) { | |||||
node *NewNode = enif_alloc(sizeof(node)); | |||||
NewNode->Key = Key; | |||||
NewNode->Value = Value; | |||||
NewNode->next = NULL; | |||||
ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head = NewNode; | |||||
ResPtr->keySize = ResPtr->keySize + 1; | |||||
//enif_fprintf(stdout, "IMY************put4444 \n"); | |||||
return ok; | |||||
} else { | |||||
node *Head = ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head; | |||||
while (Head) { | |||||
if (compareBin(Key, Head->Key) == 0) { | |||||
//ERL_NIF_TERM Ret; | |||||
ErlNifBinary OldValue = Head->Value; | |||||
//enif_binary_to_term(env, OldValue.data, OldValue.size, &Ret, 0); | |||||
Head->Value = Value; | |||||
enif_release_binary(&OldValue); | |||||
enif_release_binary(&Key); | |||||
//enif_fprintf(stdout, "IMY************put55555 \n"); | |||||
//return Ret; | |||||
return ok; | |||||
} | |||||
Head = Head->next; | |||||
} | |||||
node *NewNode = enif_alloc(sizeof(node)); | |||||
NewNode->Key = Key; | |||||
NewNode->Value = Value; | |||||
NewNode->next = ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head; | |||||
ResPtr->arrayPtr[BblIndex_1][BblIndex_2].head = NewNode; | |||||
ResPtr->keySize = ResPtr->keySize + 1; | |||||
//enif_fprintf(stdout, "IMY************put6666\n"); | |||||
return ok; | |||||
} | |||||
} | |||||
///////////////////////////////////////////////////////////////////////// | |||||
static ErlNifFunc nifFuns[] = { | |||||
{"new", 0, new}, | |||||
{"get", 2, get}, | |||||
{"put", 3, put}, | |||||
{"hash1", 2, hash1}, | |||||
{"hash2", 2, hash2}, | |||||
{"hash3", 2, hash3}, | |||||
{"compareBin1", 2, compareBin1}, | |||||
{"compareBin2", 2, compareBin2}, | |||||
}; | |||||
ERL_NIF_INIT(nifHashb, nifFuns, | |||||
&load, NULL, NULL, NULL) | |||||
@ -0,0 +1,7 @@ | |||||
{port_specs, [ | |||||
{"../../priv/nifHashb.so", ["*.c"]} | |||||
]}. | |||||
@ -0,0 +1,157 @@ | |||||
%% This file is part of khash released under the MIT license. | |||||
%% See the LICENSE file for more information. | |||||
%% Copyright 2013 Cloudant, Inc <support@cloudant.com> | |||||
-module(khash). | |||||
-on_load(init/0). | |||||
-export([ | |||||
new/0, | |||||
new/1, | |||||
from_list/1, | |||||
from_list/2, | |||||
to_list/1, | |||||
clear/1, | |||||
lookup/2, | |||||
get/2, | |||||
get/3, | |||||
put/3, | |||||
del/2, | |||||
size/1, | |||||
iter/1, | |||||
iter_next/1, | |||||
fold/3 | |||||
]). | |||||
-define(NOT_LOADED, not_loaded(?LINE)). | |||||
-type kv() :: {any(), any()}. | |||||
-type khash() :: term(). | |||||
-type khash_iter() :: term(). | |||||
-type option() :: []. | |||||
-spec new() -> {ok, khash()}. | |||||
new() -> | |||||
new([]). | |||||
-spec new([option()]) -> {ok, khash()}. | |||||
new(_Options) -> | |||||
?NOT_LOADED. | |||||
-spec from_list([kv()]) -> {ok, khash()}. | |||||
from_list(KVList) -> | |||||
from_list(KVList, []). | |||||
-spec from_list([kv()], [option()]) -> {ok, khash()}. | |||||
from_list(KVList, Options) -> | |||||
{ok, Hash} = ?MODULE:new(Options), | |||||
lists:foreach(fun({Key, Val}) -> | |||||
?MODULE:put(Hash, Key, Val) | |||||
end, KVList), | |||||
{ok, Hash}. | |||||
-spec to_list(khash()) -> [kv()]. | |||||
to_list(_Hash) -> | |||||
?NOT_LOADED. | |||||
-spec clear(khash()) -> ok. | |||||
clear(_Hash) -> | |||||
?NOT_LOADED. | |||||
-spec lookup(khash(), any()) -> {value, any()} | not_found. | |||||
lookup(Hash, Key) -> | |||||
lookup_int(Hash, erlang:phash2(Key), Key). | |||||
-spec get(khash(), any()) -> any(). | |||||
get(Hash, Key) -> | |||||
get(Hash, Key, undefined). | |||||
-spec get(khash(), any(), any()) -> any(). | |||||
get(Hash, Key, Default) -> | |||||
get_int(Hash, erlang:phash2(Key), Key, Default). | |||||
-spec put(khash(), any(), any()) -> ok. | |||||
put(Hash, Key, Value) -> | |||||
put_int(Hash, erlang:phash2(Key), Key, Value). | |||||
-spec del(khash(), any()) -> ok. | |||||
del(Hash, Key) -> | |||||
del_int(Hash, erlang:phash2(Key), Key). | |||||
-spec size(khash()) -> non_neg_integer(). | |||||
size(_Hash) -> | |||||
?NOT_LOADED. | |||||
-spec iter(khash()) -> {ok, khash_iter()}. | |||||
iter(_Hash) -> | |||||
?NOT_LOADED. | |||||
-spec iter_next(khash_iter()) -> | |||||
kv() | end_of_table | {error, expired_iterator}. | |||||
iter_next(_Iter) -> | |||||
?NOT_LOADED. | |||||
-spec fold(khash(), fun(), any()) -> any(). | |||||
fold(Hash, FoldFun, Acc) -> | |||||
{ok, Iter} = ?MODULE:iter(Hash), | |||||
fold_int(Iter, FoldFun, Acc). | |||||
fold_int(Iter, FoldFun, Acc) -> | |||||
case ?MODULE:iter_next(Iter) of | |||||
{Key, Value} -> | |||||
NewAcc = FoldFun(Key, Value, Acc), | |||||
fold_int(Iter, FoldFun, NewAcc); | |||||
end_of_table -> | |||||
Acc | |||||
end. | |||||
init() -> | |||||
PrivDir = case code:priv_dir(?MODULE) of | |||||
{error, _} -> | |||||
EbinDir = filename:dirname(code:which(?MODULE)), | |||||
AppPath = filename:dirname(EbinDir), | |||||
filename:join(AppPath, "priv"); | |||||
Path -> | |||||
Path | |||||
end, | |||||
erlang:load_nif(filename:join(PrivDir, "khash"), 0). | |||||
lookup_int(_Hash, _HashValue, _Key) -> | |||||
?NOT_LOADED. | |||||
get_int(_Hash, _HashValue, _Key, _Default) -> | |||||
?NOT_LOADED. | |||||
put_int(_Hash, _HashValue, _Key, _Value) -> | |||||
?NOT_LOADED. | |||||
del_int(_Hash, _HashValue, _Key) -> | |||||
?NOT_LOADED. | |||||
not_loaded(Line) -> | |||||
erlang:nif_error({not_loaded, [{module, ?MODULE}, {line, Line}]}). |
@ -0,0 +1,49 @@ | |||||
-module(nifArray). | |||||
-on_load(init/0). | |||||
-export([ | |||||
new/1 | |||||
, get/2 | |||||
, put/3 | |||||
, test/1 | |||||
, test1/1 | |||||
]). | |||||
-type nifArray() :: reference(). | |||||
init() -> | |||||
SoName = | |||||
case code:priv_dir(?MODULE) of | |||||
{error, _} -> | |||||
case code:which(?MODULE) of | |||||
Filename when is_list(Filename) -> | |||||
filename:join([filename:dirname(Filename), "../priv", "nifArray"]); | |||||
_ -> | |||||
filename:join("../priv", "nifArray") | |||||
end; | |||||
Dir -> | |||||
filename:join(Dir, "nifArray") | |||||
end, | |||||
erlang:load_nif(SoName, 0). | |||||
-spec new(Size :: integer()) -> nifArray(). | |||||
new(_Size) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
-spec get(Ref :: nifArray(), Index :: integer()) -> term(). | |||||
get(_Ref, _Index) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
-spec put(Ref :: nifArray(), Index :: integer(), Value :: term()) -> term(). | |||||
put(_Ref, _Index, _Value) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
test(Value) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
test1(Value) -> | |||||
Bin = term_to_binary(Value), | |||||
Term = binary_to_term(Bin). | |||||
@ -0,0 +1,77 @@ | |||||
-module(nifHashb). | |||||
-on_load(init/0). | |||||
-export([ | |||||
new/0 | |||||
, get/2 | |||||
, put/3 | |||||
, hash1/2 | |||||
, hash2/2 | |||||
, hash3/2 | |||||
, cb1/2 | |||||
, cb2/2 | |||||
, compareBin1/2 | |||||
, compareBin2/2 | |||||
]). | |||||
init() -> | |||||
SoName = | |||||
case code:priv_dir(?MODULE) of | |||||
{error, _} -> | |||||
case code:which(?MODULE) of | |||||
Filename when is_list(Filename) -> | |||||
filename:join([filename:dirname(Filename), "../priv", "nifHashb"]); | |||||
_ -> | |||||
filename:join("../priv", "nifHashb") | |||||
end; | |||||
Dir -> | |||||
filename:join(Dir, "nifHashb") | |||||
end, | |||||
erlang:load_nif(SoName, 0). | |||||
new() -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
get(Ref, Key) -> | |||||
KeyBin = erlang:term_to_binary(Key), | |||||
Hash1 = erlang:phash2(KeyBin), | |||||
Hash2 = erlang:phash2(KeyBin, 123211111), | |||||
get(Ref, Hash1, Hash2, KeyBin). | |||||
get(Ref, Hash1, Hash2, KeyBin) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
put(Ref, Key, Value) -> | |||||
KeyBin = erlang:term_to_binary(Key), | |||||
ValueBin = erlang:term_to_binary(Value), | |||||
Hash1 = erlang:phash2(KeyBin), | |||||
Hash2 = erlang:phash2(KeyBin, 123211111), | |||||
put(Ref, Hash1, Hash2, KeyBin, ValueBin). | |||||
put(Ref, Hash1, Hash2, Key, Value) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
hash1(Term, Range) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
hash2(Term, Range) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
hash3(Term, Range) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
cb1(Term1, Term2) -> | |||||
compareBin1(term_to_binary(Term1), term_to_binary(Term2)). | |||||
cb2(Term1, Term2) -> | |||||
compareBin2(term_to_binary(Term1), term_to_binary(Term2)). | |||||
compareBin1(Bin1, Bin2) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
compareBin2(Bin1, Bin2) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
@ -0,0 +1,64 @@ | |||||
-module(nifHashbbak). | |||||
-on_load(init/0). | |||||
-export([ | |||||
new/0 | |||||
, get/2 | |||||
, put/3 | |||||
, hash1/2 | |||||
, hash2/2 | |||||
, hash3/2 | |||||
, cb1/2 | |||||
, cb2/2 | |||||
, compareBin1/2 | |||||
, compareBin2/2 | |||||
]). | |||||
init() -> | |||||
SoName = | |||||
case code:priv_dir(?MODULE) of | |||||
{error, _} -> | |||||
case code:which(?MODULE) of | |||||
Filename when is_list(Filename) -> | |||||
filename:join([filename:dirname(Filename), "../priv", "nifHashb"]); | |||||
_ -> | |||||
filename:join("../priv", "nifHashb") | |||||
end; | |||||
Dir -> | |||||
filename:join(Dir, "nifHashb") | |||||
end, | |||||
erlang:load_nif(SoName, 0). | |||||
new() -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
get(Ref, Key) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
put(Ref, Key, Value) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
hash1(Term, Range) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
hash2(Term, Range) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
hash3(Term, Range) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
cb1(Term1, Term2) -> | |||||
compareBin1(term_to_binary(Term1), term_to_binary(Term2)). | |||||
cb2(Term1, Term2) -> | |||||
compareBin2(term_to_binary(Term1), term_to_binary(Term2)). | |||||
compareBin1(Bin1, Bin2) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
compareBin2(Bin1, Bin2) -> | |||||
erlang:nif_error({nif_not_loaded, module, ?MODULE, line, ?LINE}). | |||||
@ -0,0 +1,67 @@ | |||||
-module(utKhashDs). | |||||
-compile([nowarn_unused_function, nowarn_unused_vars, nowarn_export_all]). | |||||
-export([start/2]). | |||||
start(Num, Pid) -> | |||||
Ds = init(Num), | |||||
Time1 = erlang:system_time(nanosecond), | |||||
NewDsI = insert(Num, Ds), | |||||
Time2 = erlang:system_time(nanosecond), | |||||
NewDsR = read(Num, NewDsI, undefined), | |||||
Time3 = erlang:system_time(nanosecond), | |||||
NewDsU = update(Num, NewDsR), | |||||
Time4 = erlang:system_time(nanosecond), | |||||
NewDsF = for(Num, NewDsU), | |||||
Time5 = erlang:system_time(nanosecond), | |||||
delete(Num, NewDsF), | |||||
Time6 = erlang:system_time(nanosecond), | |||||
erlang:send(Pid, {over, self(), Time2 - Time1, Time3 - Time2, Time4 - Time3, Time5 - Time4, Time6 - Time5}), | |||||
exit(normal). | |||||
init(Num) -> | |||||
{ok, Ds} = khash:new(), | |||||
Ds. | |||||
insert(0, Ds) -> | |||||
Ds; | |||||
insert(Num, Ds) -> | |||||
Key = utTestDs:makeK(Num), | |||||
Value = utTestDs:makeV(Num), | |||||
khash:put(Ds, Key, Value), | |||||
insert(Num - 1, Ds). | |||||
read(0, Ds, _V) -> | |||||
Ds; | |||||
read(Num, Ds, _V) -> | |||||
Key = utTestDs:makeK(Num), | |||||
Value = khash:get(Ds, Key, undefined), | |||||
read(Num - 1, Ds, Value). | |||||
update(0, Ds) -> | |||||
Ds; | |||||
update(Num, Ds) -> | |||||
Key = utTestDs:makeK(Num), | |||||
Value = utTestDs:makeV2(Num), | |||||
khash:put(Ds, Key, Value), | |||||
update(Num - 1, Ds). | |||||
for(Num, Ds) -> | |||||
Keylist = khash:to_list(Ds), | |||||
for1(Keylist, undefined), | |||||
Ds. | |||||
for1([], V) -> | |||||
ok; | |||||
for1([H | T], _V) -> | |||||
V = erlang:get(H), | |||||
for1(T, V). | |||||
delete(0, Ds) -> | |||||
ok; | |||||
delete(Num, Ds) -> | |||||
Key = utTestDs:makeK(Num), | |||||
khash:del(Ds, Key), | |||||
delete(Num - 1, Ds). | |||||
@ -0,0 +1,60 @@ | |||||
-module(utNifArrayDs). | |||||
-compile([nowarn_unused_function, nowarn_unused_vars, nowarn_export_all]). | |||||
-export([start/2]). | |||||
start(Num, Pid) -> | |||||
Ds = init(Num), | |||||
Time1 = erlang:system_time(nanosecond), | |||||
NewDsI = insert(Num - 1, Ds), | |||||
Time2 = erlang:system_time(nanosecond), | |||||
NewDsR = read(Num - 1, Ds), | |||||
Time3 = erlang:system_time(nanosecond), | |||||
NewDsU = update(Num - 1, Ds), | |||||
Time4 = erlang:system_time(nanosecond), | |||||
NewDsF = for(Num - 1, Ds), | |||||
Time5 = erlang:system_time(nanosecond), | |||||
delete(Num - 1, Ds), | |||||
Time6 = erlang:system_time(nanosecond), | |||||
erlang:send(Pid, {over, self(), Time2 - Time1, Time3 - Time2, Time4 - Time3, Time5 - Time4, not_support}), | |||||
exit(normal). | |||||
init(Num) -> | |||||
nifArray:new(Num). | |||||
insert(0, Ds) -> | |||||
% Key = utTestDs:makeK(0), | |||||
nifArray:put(Ds, 0, utTestDs:makeV(0)); | |||||
insert(Num, Ds) -> | |||||
% Key = utTestDs:makeK(Num), | |||||
nifArray:put(Ds, Num, utTestDs:makeV(Num)), | |||||
insert(Num - 1, Ds). | |||||
read(0, Ds) -> | |||||
% Key = utTestDs:makeK(0), | |||||
Value = nifArray:get(Ds, 0), | |||||
Ds; | |||||
read(Num, Ds) -> | |||||
% Key = utTestDs:makeK(Num), | |||||
Value = nifArray:get(Ds, Num), | |||||
read(Num - 1, Ds). | |||||
update(0, Ds) -> | |||||
% Key = utTestDs:makeK(0), | |||||
nifArray:put(Ds, 0, utTestDs:makeV2(0)); | |||||
update(Num, Ds) -> | |||||
% Key = utTestDs:makeK(Num), | |||||
nifArray:put(Ds, Num, utTestDs:makeV2(Num)), | |||||
update(Num - 1, Ds). | |||||
for(0, Ds) -> | |||||
Value = nifArray:get(Ds, 0), | |||||
Ds; | |||||
for(Num, Ds) -> | |||||
Value = nifArray:get(Ds, Num), | |||||
for(Num - 1, Ds). | |||||
delete(Num, Ds) -> | |||||
ok. | |||||
@ -0,0 +1,55 @@ | |||||
-module(utNifHashbDs). | |||||
-compile([nowarn_unused_function, nowarn_unused_vars, nowarn_export_all]). | |||||
-export([start/2]). | |||||
start(Num, Pid) -> | |||||
Ds = init(Num), | |||||
Time1 = erlang:system_time(nanosecond), | |||||
NewDsI = insert(Num, Ds), | |||||
Time2 = erlang:system_time(nanosecond), | |||||
NewDsR = read(Num, Ds, undefined), | |||||
Time3 = erlang:system_time(nanosecond), | |||||
NewDsU = update(Num, Ds), | |||||
Time4 = erlang:system_time(nanosecond), | |||||
NewDsF = for(Num, Ds), | |||||
Time5 = erlang:system_time(nanosecond), | |||||
delete(Num, Ds), | |||||
Time6 = erlang:system_time(nanosecond), | |||||
erlang:send(Pid, {over, self(), Time2 - Time1, Time3 - Time2, Time4 - Time3, Time5 - Time4, Time6 - Time5}), | |||||
exit(normal). | |||||
init(Num) -> | |||||
nifHashb:new(). | |||||
insert(0, Ds) -> | |||||
Ds; | |||||
insert(Num, Ds) -> | |||||
Key = utTestDs:makeK(Num), | |||||
Value = utTestDs:makeV(Num), | |||||
nifHashb:put(Ds, Key, Value), | |||||
insert(Num - 1, Ds). | |||||
read(0, Ds, _V) -> | |||||
Ds; | |||||
read(Num, Ds, _V) -> | |||||
Key = utTestDs:makeK(Num), | |||||
Value = nifHashb:get(Ds, Key), | |||||
read(Num - 1, Ds, Value). | |||||
update(0, Ds) -> | |||||
Ds; | |||||
update(Num, Ds) -> | |||||
Key = utTestDs:makeK(Num), | |||||
Value = utTestDs:makeV2(Num), | |||||
nifHashb:put(Ds, Key, Value), | |||||
update(Num - 1, Ds). | |||||
for(Num, Ds) -> | |||||
Ds. | |||||
delete(Num, Ds) -> | |||||
ok. | |||||