684 lines
16 KiB
C
684 lines
16 KiB
C
/***************************************************************************
|
|
* Copyright 1995 Michael Veksler. mveksler@vnet.ibm.com
|
|
***************************************************************************
|
|
* File: generic_hash.c
|
|
* Purpose : dynamically growing hash, may use shared or local memory.
|
|
***************************************************************************
|
|
*/
|
|
#ifdef CONFIG_IPC
|
|
|
|
#include <sys/types.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include "generic_hash.h"
|
|
|
|
#define ROUND_UP4(num) (( (num)+3) & ~3)
|
|
|
|
#define FREE_ENTRY 0
|
|
#define DELETED_ENTRY ((DWORD)-1)
|
|
|
|
#define NO_OF_PRIMES 512
|
|
#define GET_ITEM(items,size,i)\
|
|
(*(HASH_ITEM*) \
|
|
( ((char *)(items))+ \
|
|
(i)*(size)) )
|
|
|
|
static HASH_ITEM *locate_entry(HASH_CONTAINER* hash, DWORD key,
|
|
HASH_VAL *seeked_data, BOOL skip_deleted);
|
|
|
|
static void copy_hash_items(HASH_CONTAINER *hash, HASH_ITEM *old_items,
|
|
int old_n_items);
|
|
|
|
static BOOL arrays_initialized = FALSE;
|
|
static int primes[NO_OF_PRIMES];
|
|
static int best_primes[NO_OF_PRIMES];
|
|
static int no_of_primes;
|
|
static int no_of_best_primes;
|
|
static int max_num;
|
|
|
|
/* binary search for `num' in the `primes' array */
|
|
static BOOL prime_binary_search_found(int num)
|
|
{
|
|
int min_idx, max_idx, idx;
|
|
|
|
min_idx=0;
|
|
max_idx=no_of_primes-1;
|
|
|
|
while (min_idx <= max_idx) {
|
|
idx = (max_idx + min_idx) >> 1;
|
|
if (num == primes[idx])
|
|
return TRUE;
|
|
if (num < primes[idx])
|
|
max_idx = idx-1;
|
|
else
|
|
min_idx = idx+1;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
static BOOL is_prime(int num)
|
|
{
|
|
int i;
|
|
if ((num & 0x1) == 0) /* can be divided by 2 */
|
|
if (num == 2)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
|
|
if (num <= primes[no_of_primes-1])
|
|
return prime_binary_search_found(num);
|
|
|
|
for (i=0 ; i < no_of_primes ; i++) {
|
|
if (num % primes[i] == 0)
|
|
return FALSE;
|
|
if (num < primes[i] * primes[i])
|
|
return TRUE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static void setup_primes()
|
|
{
|
|
int num;
|
|
|
|
primes[0]=2;
|
|
primes[1]=3;
|
|
no_of_primes=2;
|
|
|
|
/* count in modulo 6 to avoid numbers that divide by 2 or 3 */
|
|
for (num=5 ; ; num+=6) {
|
|
if (is_prime(num)) {
|
|
primes[no_of_primes++]=num;
|
|
if (no_of_primes >= NO_OF_PRIMES)
|
|
break;
|
|
}
|
|
if (is_prime(num+2)) {
|
|
primes[no_of_primes++]=num+2;
|
|
if (no_of_primes >= NO_OF_PRIMES)
|
|
break;
|
|
}
|
|
}
|
|
max_num= primes[no_of_primes-1] * primes[no_of_primes-1];
|
|
}
|
|
|
|
|
|
/* Find primes which are far "enough" from powers of two */
|
|
|
|
void setup_best_primes()
|
|
{
|
|
int i;
|
|
int num;
|
|
int pow2before, pow2after;
|
|
int min_range, max_range;
|
|
|
|
min_range=3;
|
|
max_range=3;
|
|
pow2before= 2;
|
|
pow2after= 4;
|
|
|
|
no_of_best_primes= 0;
|
|
for (i=0 ; i < no_of_primes ; i++){
|
|
num= primes[i];
|
|
|
|
if (num > pow2after) {
|
|
pow2before= pow2after;
|
|
pow2after <<=1;
|
|
min_range= pow2before+ (pow2before >> 3);
|
|
max_range= pow2after- (pow2before >> 2);
|
|
}
|
|
if (num > min_range && num < max_range)
|
|
best_primes[no_of_best_primes++]=num;
|
|
}
|
|
}
|
|
|
|
/* binary search for `num' in the `best_primes' array,
|
|
* Return smallest best_prime >= num.
|
|
*/
|
|
static int best_prime_binary_search(int num)
|
|
{
|
|
int min_idx, max_idx, idx;
|
|
|
|
min_idx=0;
|
|
max_idx=no_of_best_primes-1;
|
|
|
|
while (1) {
|
|
idx = (max_idx + min_idx) >> 1;
|
|
if (num == best_primes[idx])
|
|
return num;
|
|
if (num < best_primes[idx]) {
|
|
max_idx = idx-1;
|
|
if (max_idx <= min_idx)
|
|
return best_primes[idx];
|
|
}
|
|
else {
|
|
min_idx = idx+1;
|
|
if (min_idx >= max_idx)
|
|
return best_primes[max_idx];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* Find the best prime, near `num' (which can be any number) */
|
|
static int best_prime(int num)
|
|
{
|
|
int log2;
|
|
int pow2less, pow2more;
|
|
int min_range, max_range;
|
|
|
|
if (num < 11)
|
|
return 11;
|
|
|
|
if (num <= best_primes[no_of_best_primes-1])
|
|
return best_prime_binary_search(num);
|
|
|
|
assert( num < max_num );
|
|
|
|
for (log2=0 ; num >> log2 ; log2++)
|
|
;
|
|
|
|
pow2less= 1 << log2;
|
|
pow2more= 1 << (log2+1);
|
|
min_range= pow2less + (pow2less >> 3);
|
|
max_range= pow2more - (pow2more >> 3);
|
|
|
|
if (num < min_range)
|
|
num= min_range;
|
|
|
|
num |= 1; /* make sure num can't be divided by 2 */
|
|
|
|
while (1) {
|
|
if (num >= max_range) {
|
|
pow2less<<= 1;
|
|
pow2more<<= 1;
|
|
min_range= pow2less + (pow2less >> 3);
|
|
max_range= pow2more - (pow2more >> 3);
|
|
num= min_range | 1; /* make sure num can't be divided by 2 */
|
|
}
|
|
/* num should be here in the range: (min_range, max_range) */
|
|
if (is_prime(num))
|
|
return num;
|
|
num+=2;
|
|
}
|
|
}
|
|
|
|
/* FIXME: This can be done before compiling. (uning a script)*/
|
|
static void setup_arrays()
|
|
{
|
|
setup_primes();
|
|
setup_best_primes();
|
|
}
|
|
|
|
/* Discard all DELETED_ENTRYs moving the data to it's correct location.
|
|
* Done without a temporary buffer.
|
|
* May require some efficiency improvements ( currently it's o(N^2)
|
|
* or is it o(N^3) in the worst case ? In the avarege it seems to be
|
|
* something like o(N log (N)))
|
|
*/
|
|
static void static_collect_garbge(HASH_CONTAINER *hash)
|
|
{
|
|
int i;
|
|
BOOL change;
|
|
HASH_ITEM *items;
|
|
HASH_ITEM *located;
|
|
HASH_ITEM *item;
|
|
int key;
|
|
|
|
items= hash->items;
|
|
|
|
do {
|
|
change= FALSE;
|
|
for (i=hash->shared->total_items-1 ; i >= 0 ; i--) {
|
|
item= &GET_ITEM(items,hash->bytes_per_item,i);
|
|
key= item->key;
|
|
if (key != DELETED_ENTRY && key != FREE_ENTRY) {
|
|
/* try to place the entry in a deleted location */
|
|
located= locate_entry(hash, key, &item->data,
|
|
0 /* no skip_deleted */);
|
|
if (located->key == DELETED_ENTRY) {
|
|
change= TRUE;
|
|
memcpy(&located, &item,
|
|
hash->bytes_per_item);
|
|
item->key= DELETED_ENTRY;
|
|
}
|
|
}
|
|
}
|
|
} while (change);
|
|
|
|
/* No change means that there is no need to go through a DELETED_ENTRY
|
|
* in order to reach an item, so DELETED_ENTRY looses it's special
|
|
* meaning, and it is the same as FREE_ENTRY.
|
|
*/
|
|
for (i=hash->shared->total_items-1 ; i >= 0 ; i--)
|
|
if (GET_ITEM(items,hash->bytes_per_item,i).key == DELETED_ENTRY)
|
|
GET_ITEM(items,hash->bytes_per_item,i).key = FREE_ENTRY;
|
|
hash->shared->deleted_items=0;
|
|
}
|
|
|
|
static void collect_garbge(HASH_CONTAINER *hash)
|
|
{
|
|
HASH_SHARED *shared= hash->shared;
|
|
HASH_ITEM *temp_items;
|
|
int size;
|
|
|
|
size= shared->total_items * hash->bytes_per_item;
|
|
temp_items= (HASH_ITEM*)malloc(size);
|
|
if (temp_items==NULL) {
|
|
static_collect_garbge(hash);
|
|
} else {
|
|
memcpy(temp_items, hash->items, size);
|
|
copy_hash_items(hash, temp_items, shared->total_items);
|
|
}
|
|
}
|
|
|
|
|
|
static void copy_hash_items(HASH_CONTAINER *hash, HASH_ITEM *old_items,
|
|
int old_n_items)
|
|
{
|
|
HASH_SHARED *shared= hash->shared;
|
|
HASH_ITEM *item;
|
|
int i;
|
|
|
|
shared->deleted_items = 0;
|
|
shared->free_items= shared->total_items;
|
|
|
|
/* make all items free */
|
|
for (i= shared->total_items-1 ; i>=0 ; i--)
|
|
GET_ITEM(hash->items, hash->bytes_per_item, i).key = FREE_ENTRY;
|
|
|
|
/* copy items */
|
|
for (i=0 ; i <= old_n_items; i++) {
|
|
item= &GET_ITEM(old_items, hash->bytes_per_item,i);
|
|
if (item->key != FREE_ENTRY && item->key != DELETED_ENTRY)
|
|
hash_add_item(hash, item->key, &item->data);
|
|
}
|
|
}
|
|
|
|
|
|
static void reorder_hash(HASH_CONTAINER *hash)
|
|
{
|
|
HASH_SHARED *shared= hash->shared;
|
|
HASH_ITEM *items, *old_items;
|
|
HASH_PTR shared_items, old_shared_items;
|
|
int n_items, old_n_items;
|
|
int size;
|
|
|
|
if (shared->deleted_items > hash->min_free_items) {
|
|
collect_garbge(hash);
|
|
return;
|
|
}
|
|
n_items= best_prime(shared->total_items * HASH_REALLOC_JUMPS);
|
|
|
|
size= n_items *
|
|
(sizeof(items[0]) - sizeof(items[0].data) + hash->bytes_per_item);
|
|
|
|
shared_items= hash->allocate_mem(size);
|
|
items= hash->access_mem(shared_items);
|
|
|
|
if (items == NULL) {
|
|
collect_garbge(hash);
|
|
return;
|
|
}
|
|
old_shared_items = shared->items;
|
|
old_n_items= shared->total_items;
|
|
old_items= hash->items;
|
|
|
|
/* setup a new clean hash based on the parameters of the original one */
|
|
hash->items= items;
|
|
shared->total_items = n_items;
|
|
shared->items= shared_items;
|
|
set_hash_parameters(hash, hash->maximum_load);
|
|
copy_hash_items(hash, old_items, old_n_items);
|
|
|
|
hash->free_mem(old_shared_items);
|
|
hash->last_ptr_update= ++shared->ptr_updates;
|
|
}
|
|
|
|
/* low level: attach hash existing hash items, no checks are performed
|
|
* No complex calculations done.
|
|
*/
|
|
static HASH_CONTAINER *attach_no_check(HASH_ITEM *items, int bytes_per_datum)
|
|
{
|
|
HASH_CONTAINER *hash;
|
|
int bytes_per_item;
|
|
HASH_ITEM dummy_item;
|
|
|
|
hash= (HASH_CONTAINER*) malloc(sizeof(HASH_CONTAINER) );
|
|
if (hash == NULL)
|
|
return NULL;
|
|
|
|
bytes_per_item= bytes_per_datum+
|
|
sizeof(dummy_item)-sizeof(dummy_item.data);
|
|
hash->bytes_per_item= ROUND_UP4(bytes_per_item);
|
|
hash->items= items;
|
|
hash->is_correct_item= NULL;
|
|
hash->allocate_mem= HASH_MEM_ALLOC;
|
|
hash->access_mem= HASH_MEM_ACCESS;
|
|
hash->free_mem= HASH_MEM_FREE;
|
|
set_hash_parameters(hash, HASH_LOAD);
|
|
|
|
|
|
return hash;
|
|
}
|
|
|
|
|
|
/* Attach existing & running remote (i.e. shared) hash.
|
|
* Attach the items using the data stored in "shared"
|
|
*/
|
|
HASH_CONTAINER *attach_remote_hash(HASH_SHARED *shared, int bytes_per_datum,
|
|
HASH_ITEM *(*access_mem)(HASH_PTR))
|
|
{
|
|
HASH_CONTAINER *hash;
|
|
HASH_ITEM *items;
|
|
|
|
assert(access_mem != NULL);
|
|
if (! arrays_initialized)
|
|
setup_arrays();
|
|
|
|
items=access_mem(shared->items);
|
|
hash= attach_no_check(items, bytes_per_datum);
|
|
if (hash == NULL)
|
|
return NULL;
|
|
|
|
hash->shared_was_malloced = FALSE;
|
|
hash->shared= shared;
|
|
return (hash);
|
|
}
|
|
|
|
|
|
HASH_CONTAINER *create_remote_hash(HASH_SHARED *shared,
|
|
int bytes_per_datum,
|
|
int total_items,
|
|
HASH_PTR (*allocate_mem)(int size),
|
|
HASH_ITEM *(*access_mem)(HASH_PTR))
|
|
{
|
|
HASH_CONTAINER *hash;
|
|
int size;
|
|
int i;
|
|
|
|
assert(total_items >= 1);
|
|
assert(bytes_per_datum >=1);
|
|
assert(access_mem != NULL);
|
|
assert(allocate_mem != NULL);
|
|
assert(shared != NULL);
|
|
if (! arrays_initialized)
|
|
setup_arrays();
|
|
|
|
if (total_items < MIN_HASH)
|
|
total_items= MIN_HASH;
|
|
else
|
|
total_items= best_prime(total_items);
|
|
|
|
hash= attach_no_check(NULL, bytes_per_datum);
|
|
|
|
if (hash==NULL) {
|
|
free(hash);
|
|
return NULL;
|
|
}
|
|
|
|
shared->total_items= total_items;
|
|
hash->shared= shared;
|
|
hash->shared_was_malloced = FALSE;
|
|
|
|
size= total_items * hash->bytes_per_item;
|
|
|
|
shared->items = allocate_mem(size);
|
|
hash->items= access_mem(shared->items);
|
|
|
|
if (hash->items == NULL ) {
|
|
free(hash);
|
|
return NULL;
|
|
}
|
|
shared->items.ptr= hash->items;
|
|
|
|
/* make all items free */
|
|
for (i=0 ; i < total_items ; i++)
|
|
GET_ITEM(hash->items,hash->bytes_per_item,i).key = FREE_ENTRY;
|
|
|
|
shared->deleted_items= 0;
|
|
shared->free_items= total_items;
|
|
shared->ptr_updates= 0;
|
|
return hash;
|
|
|
|
}
|
|
|
|
/* hash constructor: create brand new hash */
|
|
HASH_CONTAINER *create_hash(int bytes_per_datum, int total_items)
|
|
{
|
|
HASH_CONTAINER *hash;
|
|
HASH_SHARED *shared;
|
|
|
|
|
|
shared= (HASH_SHARED*)malloc(sizeof(HASH_SHARED));
|
|
if (shared == NULL)
|
|
return NULL;
|
|
|
|
hash= create_remote_hash(shared, bytes_per_datum, total_items,
|
|
HASH_MEM_ALLOC, HASH_MEM_ACCESS);
|
|
|
|
if (hash == NULL) {
|
|
free(shared);
|
|
return NULL;
|
|
}
|
|
|
|
hash->shared_was_malloced = TRUE;
|
|
return hash;
|
|
}
|
|
|
|
/* set the extra handlers to non default values */
|
|
void set_hash_handlers(HASH_CONTAINER *hash,
|
|
HASH_ITEM_TEST *is_correct_item,
|
|
HASH_PTR (*allocate_mem)(int size),
|
|
void (*free_mem)(HASH_PTR),
|
|
HASH_ITEM *(*access_mem)(HASH_PTR))
|
|
{
|
|
assert(hash);
|
|
assert(allocate_mem);
|
|
assert(free_mem);
|
|
|
|
hash->free_mem = free_mem;
|
|
hash->allocate_mem = allocate_mem;
|
|
hash->access_mem = access_mem;
|
|
hash->is_correct_item = is_correct_item;
|
|
}
|
|
|
|
|
|
/* set extra parameters */
|
|
void set_hash_parameters(HASH_CONTAINER *hash, int load)
|
|
{
|
|
assert(hash);
|
|
assert(load>30); /* no sence to realloc with less than */
|
|
/* 50% load, limiting to 30% to be on */
|
|
/* the safe size */
|
|
assert(load<=100);
|
|
|
|
hash->maximum_load= load;
|
|
hash->min_free_items= (1.0 - load/100.0) * hash->shared->total_items + 1 ;
|
|
}
|
|
|
|
/* hash destructor: destroy anything related to the hash */
|
|
void destroy_hash(HASH_CONTAINER *hash)
|
|
{
|
|
assert(hash);
|
|
hash->free_mem(hash->shared->items);
|
|
if (hash->shared_was_malloced)
|
|
free(hash->shared);
|
|
free(hash);
|
|
}
|
|
|
|
/* hash destructor: just detach hash, without destroing it (makes */
|
|
/* sence in shared memory environment) */
|
|
void detach_hash(HASH_CONTAINER *hash)
|
|
{
|
|
assert(hash);
|
|
free(hash);
|
|
}
|
|
|
|
|
|
/********** Hash usage *************/
|
|
static inline BOOL
|
|
correct_entry(HASH_ITEM *item, int key, HASH_VAL *seeked_data,
|
|
HASH_ITEM_TEST *is_correct_item, BOOL skip_deleted)
|
|
{
|
|
switch(item->key) {
|
|
case FREE_ENTRY:
|
|
return TRUE;
|
|
|
|
case DELETED_ENTRY:
|
|
return skip_deleted ? FALSE : TRUE;
|
|
|
|
default:
|
|
if (item->key != key)
|
|
return FALSE;
|
|
if (is_correct_item != NULL)
|
|
return is_correct_item(&item->data, seeked_data);
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
}
|
|
|
|
/* The algorithm of the hash (one of the 2 standard hash implementations):
|
|
* Iterate through the hash table until
|
|
* 1. The entry has been found.
|
|
* 2. A FREE entry has been found.
|
|
* 3. For insert operations only- A DELETED entry has been found.
|
|
* The difference between DELETED and FREE entires is that
|
|
* DELETED entry was one occupied, while FREE was never allocated.
|
|
* The idea behind this structure to keep other entries reachable.
|
|
*/
|
|
|
|
static HASH_ITEM *locate_entry(HASH_CONTAINER* hash, DWORD key,
|
|
HASH_VAL *seeked_data, BOOL skip_deleted)
|
|
{
|
|
DWORD hash_idx, hash_leaps;
|
|
HASH_ITEM *item;
|
|
int i;
|
|
int total_items;
|
|
|
|
assert(hash);
|
|
|
|
total_items= hash->shared->total_items;
|
|
hash_idx= key % total_items;
|
|
|
|
item= &GET_ITEM(hash->items, hash->bytes_per_item, hash_idx);
|
|
|
|
if ( correct_entry( item, key, seeked_data,
|
|
hash->is_correct_item, skip_deleted) )
|
|
return item;
|
|
|
|
/* get the WORDs in different order in this DWORD to avoid clustering */
|
|
hash_leaps=((DWORD)MAKELONG(HIWORD(key), LOWORD(key))
|
|
% (total_items-1)) +1;
|
|
|
|
/* interate through the hash table using hash_leaps */
|
|
for (i= total_items ; i ; i--) {
|
|
hash_idx+= hash_leaps;
|
|
if (hash_idx > total_items)
|
|
hash_idx -= total_items;
|
|
|
|
item= &GET_ITEM(hash->items,hash->bytes_per_item, hash_idx);
|
|
if ( correct_entry( item, key, seeked_data,
|
|
hash->is_correct_item, skip_deleted) )
|
|
return item;
|
|
}
|
|
return NULL;
|
|
|
|
}
|
|
|
|
static inline void sync_shared_hash(HASH_CONTAINER *hash)
|
|
{
|
|
HASH_SHARED *shared= hash->shared;
|
|
|
|
if (shared->ptr_updates == hash->last_ptr_update)
|
|
return;
|
|
|
|
assert(shared->ptr_updates >= hash->last_ptr_update);
|
|
hash->last_ptr_update= shared->ptr_updates;
|
|
hash->min_free_items= (1.0 - hash->maximum_load/100.0) *
|
|
shared->total_items + 1 ;
|
|
|
|
hash->items= hash->access_mem(shared->items);
|
|
}
|
|
|
|
HASH_VAL *hash_locate_item(HASH_CONTAINER* hash,
|
|
int key, HASH_VAL *seeked_data)
|
|
{
|
|
HASH_ITEM *item;
|
|
|
|
assert(hash != NULL);
|
|
sync_shared_hash(hash);
|
|
|
|
item= locate_entry(hash, key, seeked_data, 1 /* skip_deleted */);
|
|
if (item == NULL)
|
|
return NULL;
|
|
if (item->key == FREE_ENTRY )
|
|
return NULL;
|
|
|
|
return &item->data;
|
|
}
|
|
|
|
|
|
BOOL hash_add_item(HASH_CONTAINER* hash, int key, HASH_VAL *data)
|
|
{
|
|
HASH_SHARED *shared;
|
|
HASH_ITEM *item;
|
|
|
|
assert(hash != NULL);
|
|
|
|
sync_shared_hash(hash);
|
|
shared= hash->shared;
|
|
|
|
item=locate_entry(hash, key, data, 0 /* no skip_deleted */);
|
|
assert(item != NULL);
|
|
if (item->key == key)
|
|
return FALSE;
|
|
if (item->key == FREE_ENTRY)
|
|
shared->free_items--;
|
|
else
|
|
shared->deleted_items--;
|
|
|
|
item->key= key;
|
|
memcpy(&item->data, data, hash->bytes_per_item-sizeof(key));
|
|
|
|
if (shared->free_items < hash->min_free_items ||
|
|
shared->deleted_items > hash->min_free_items)
|
|
reorder_hash(hash);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOL hash_delete_item(HASH_CONTAINER* hash, int key, HASH_VAL *seeked_data)
|
|
{
|
|
HASH_ITEM *item;
|
|
|
|
assert(hash != NULL);
|
|
sync_shared_hash(hash);
|
|
|
|
item=locate_entry(hash, key, seeked_data, 1 /* skip_deleted */);
|
|
if (item == NULL)
|
|
return FALSE;
|
|
|
|
if (item->key == FREE_ENTRY)
|
|
return FALSE;
|
|
|
|
item->key = DELETED_ENTRY;
|
|
hash->shared->deleted_items++;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void *ret_null()
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
|
|
HASH_ITEM *access_local_hash(HASH_PTR ptr)
|
|
{
|
|
return ptr.ptr;
|
|
}
|
|
|
|
#endif /* CONFIG_IPC */
|