| /*************************************************************************** | |
| * 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, BOOL32 skip_deleted); | |
| static void copy_hash_items(HASH_CONTAINER *hash, HASH_ITEM *old_items, | |
| int old_n_items); | |
| static BOOL32 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 BOOL32 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 BOOL32 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; | |
| BOOL32 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__ BOOL32 | |
| correct_entry(HASH_ITEM *item, int key, HASH_VAL *seeked_data, | |
| HASH_ITEM_TEST *is_correct_item, BOOL32 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, BOOL32 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; | |
| } | |
| BOOL32 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; | |
| } | |
| BOOL32 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 */ |