/*************************************************************************** * 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 #include #include #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 */