/* * Implement an LRUMIN replacement cache policy * * See the comments in gtcache.h for API documentation. * * The LRUMIN eviction should be equivalent to the * following psuedocode. * * while more space needs to be cleared * Let n be the smallest integer such that 2^n >= space needed * If there is an entry of size >= 2^n, * delete the least recently used entry of size >= 2^n * Otherwise, let m be the smallest inetger such that there an entry of size >= 2^m * delete the least recently used item of size >=2^m */ #include <stdlib.h> #include <string.h> #include <sys/time.h> #include "gtcache.h" #include "hshtbl.h" #include "indexminpq.h" #include "steque.h" typedef struct { int id; char *url; char *data; size_t size; struct timeval last_access; } GT_ST_ENTRY; GT_ST_ENTRY *gt_cache_entries; hshtbl_t gt_hash_tbl; indexminpq_t *gt_minpq; steque_t gt_id_list; size_t gt_mem_used; size_t gt_num_slots; size_t gt_capacity; int gt_num_bands; int gt_band_size; int mylog2(int x) { unsigned int r = 0; while( x >>= 1 ) r++; return r; } int compar_a_tate_or(indexminpq_key a, indexminpq_key b) { struct timeval i = ((GT_ST_ENTRY*)a)->last_access; struct timeval j = ((GT_ST_ENTRY*)b)->last_access; if(i.tv_sec == j.tv_sec) return i.tv_usec - j.tv_usec; return i.tv_sec - j.tv_sec; } int gtcache_init(size_t capacity, size_t min_entry_size, int num_levels) { int i; gt_capacity = capacity; gt_mem_used = 0; gt_num_slots = capacity / min_entry_size; gt_num_bands = num_levels; // gt_band_size = (int)capacity / num_levels; gt_band_size = 1024; if( (gt_cache_entries = (GT_ST_ENTRY*)malloc( sizeof(GT_ST_ENTRY) * gt_num_slots )) == NULL ) return -1; if( (gt_minpq = (indexminpq_t*)malloc( sizeof(indexminpq_t) * num_levels )) == NULL ) return -1; hshtbl_init(&gt_hash_tbl, 2 * gt_num_slots); steque_init(&gt_id_list); for(i = 0; i < num_levels; i++) indexminpq_init(&gt_minpq[i], 2 * gt_num_slots, compar_a_tate_or); // fill our index stack and NULL out pointer in cache for(i = gt_num_slots - 1; i >= 0; i--) { steque_push(&gt_id_list, (steque_item)i); gt_cache_entries[i].data = NULL; gt_cache_entries[i].url = NULL; } return 0; } void* gtcache_get(char *key, size_t *val_size) { int i, band; char *data; GT_ST_ENTRY *item; struct timeval tv; item = hshtbl_get(&gt_hash_tbl, key); if(item == NULL) return NULL; data = (char*)calloc( item->size + 1, sizeof( char ) ); memcpy(data, item->data, item->size); if(val_size != NULL) *val_size = item->size; gettimeofday(&tv, NULL); item->last_access = tv; // figure out which minq it's in // i = (item->size / gt_band_size); i = mylog2(item->size) - 9; band = i < gt_num_bands ? i : gt_num_bands - 1; indexminpq_increasekey(&gt_minpq[band], item->id, item); return data; } int which_is_min(struct timeval a, struct timeval b) { if(a.tv_sec == b.tv_sec) { if(a.tv_usec < b.tv_usec) return 0; return 1; } if(a.tv_sec < b.tv_sec) return 0; return 1; } int gtcache_set_node_remover(int start_band, int end_band) { int id, i, q; GT_ST_ENTRY *item; struct timeval tv; q = -1; gettimeofday(&tv, NULL); for(i = start_band; i >= end_band; i--) { if(indexminpq_size(&gt_minpq[i]) > 0) { item = indexminpq_minkey(&gt_minpq[i]); if(which_is_min(item->last_access, tv) == 0) { tv = item->last_access; q = i; } } } if(q > -1) { // we have one from the 'above' bands id = indexminpq_delmin(&gt_minpq[q]); hshtbl_delete(&gt_hash_tbl, gt_cache_entries[id].url); free(gt_cache_entries[id].data); free(gt_cache_entries[id].url); gt_cache_entries[id].data = NULL; gt_cache_entries[id].url = NULL; steque_push(&gt_id_list, (steque_item)id); gt_mem_used -= gt_cache_entries[id].size; } else { // recurse, dropping one band if(end_band == 0) return -1; return gtcache_set_node_remover(end_band - 1, end_band - 1); } return id; } int gtcache_set(char *key, void *value, size_t val_size) { // if steque_size == 0 //delete from cache // while cursize + val_size > capacity //delete from cache // grab an id // insert into cache // insert into hashtable // insert into minq GT_ST_ENTRY *item; int new_id, i, band, old_band; struct timeval tv; size_t old_item_size; // check the size first if(val_size > gt_capacity) { // this thing is not going to fit no matter what we do // so don't mess up our beautiful cache by removing everything return 1; } gettimeofday(&tv, NULL); // figure out which minq it will be in // i = (val_size / gt_band_size); i = mylog2(val_size) - 9; band = i < gt_num_bands ? i : gt_num_bands - 1; // see if it's already there first. item = hshtbl_get(&gt_hash_tbl, key); if(item != NULL) { // replace new_id = item->id; old_item_size = gt_cache_entries[item->id].size; while(gt_mem_used - old_item_size + val_size > gt_capacity && steque_size(&gt_id_list) < gt_num_slots) { if(gtcache_set_node_remover(gt_num_bands - 1, band + 1) == new_id) // oops. we removed ourselves goto doadd; } if(steque_size(&gt_id_list) == gt_num_slots) // we removed everything including ourselves goto doadd; gt_cache_entries[item->id].size = val_size; gt_cache_entries[item->id].data = (char*)realloc( gt_cache_entries[item->id].data, val_size * sizeof( char ) ); memcpy(gt_cache_entries[item->id].data, value, val_size); gt_cache_entries[item->id].last_access = tv; // see if the band changed // i = (old_item_size / gt_band_size); i = mylog2(old_item_size) - 9; old_band = i < gt_num_bands ? i : gt_num_bands - 1; if(band != old_band) { // we need to move it to another queue indexminpq_delete(&gt_minpq[old_band], item->id); indexminpq_insert(&gt_minpq[band], item->id, &gt_cache_entries[item->id]); } else indexminpq_changekey(&gt_minpq[band], item->id, item); gt_mem_used += val_size; return 0; } doadd: if(steque_size(&gt_id_list) == 0) gtcache_set_node_remover(gt_num_bands - 1, band + 1); while(gt_mem_used + val_size > gt_capacity && steque_size(&gt_id_list) < gt_num_slots) gtcache_set_node_remover(gt_num_bands - 1, band + 1); // get a new ID new_id = (int)steque_pop(&gt_id_list); // setup a new entry gt_cache_entries[new_id].id = new_id; gt_cache_entries[new_id].size = val_size; gt_cache_entries[new_id].last_access = tv; gt_cache_entries[new_id].data = (char*)malloc( val_size * sizeof( char ) ); memcpy(gt_cache_entries[new_id].data, value, val_size); gt_cache_entries[new_id].url = (char*)calloc( strlen(key) + 1, sizeof( char ) ); strcpy(gt_cache_entries[new_id].url, key); gt_mem_used += val_size; // setup hash table and minq hshtbl_put(&gt_hash_tbl, key, &gt_cache_entries[new_id]); indexminpq_insert(&gt_minpq[band], new_id, &gt_cache_entries[new_id]); return 0; } int gtcache_memused() { return gt_mem_used; } void gtcache_destroy_helper() { // go through cache and free all data and url elements int i; for(i = 0; i < gt_num_slots; i++) { if(gt_cache_entries[i].data != NULL) free(gt_cache_entries[i].data); if(gt_cache_entries[i].url != NULL) free(gt_cache_entries[i].url); } } void gtcache_destroy() { int i; for(i = 0; i < gt_num_bands; i++) indexminpq_destroy(&gt_minpq[i]); free(gt_minpq); steque_destroy(&gt_id_list); hshtbl_destroy(&gt_hash_tbl); gtcache_destroy_helper(); free(gt_cache_entries); }