Uploaded by myfitbitacct

LRUMIN Cache

advertisement
/*
* 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);
}
Download