/* * CacheHash Copyright 2014 Regents of the University of Michigan * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy * of the License at http://www.apache.org/licenses/LICENSE-2.0 */ #include "cachehash.h" #include #include #include #include #include #include #include #define EVICTION_NEEDED 1 #define EVICTION_UNNEC 0 // doubly-linked-list node typedef struct node { struct node *next; struct node *prev; void *key; size_t keylen; void *data; } node_t; // data structure that contains the enterity of a linked list // we typedef this as cachehash in cachehash.h s.t. external interface is clean struct cachehash_s { Pvoid_t judy; void *malloced; node_t *start; node_t *end; node_t *curr_end; size_t maxsize; size_t currsize; cachehash_process_cb *evict_cb; }; cachehash *cachehash_init(size_t maxitems, cachehash_process_cb *cb) { assert(maxitems > 0); cachehash *retv = malloc(sizeof(cachehash)); assert(retv); memset(retv, 0, sizeof(cachehash)); // allocate nodes all at once to avoid fragmented memory node_t *nodes = calloc(maxitems, sizeof(node_t)); retv->malloced = nodes; assert(nodes); retv->start = nodes; retv->curr_end = nodes; retv->end = &nodes[maxitems - 1]; retv->maxsize = maxitems; retv->currsize = 0; // initial node nodes[0].next = &nodes[1]; // middle nodes for (unsigned int i = 1; i < maxitems - 1; i++) { nodes[i].prev = &nodes[i - 1]; nodes[i].next = &nodes[i + 1]; } // last node nodes[maxitems - 1].prev = &nodes[maxitems - 2]; retv->evict_cb = cb; return retv; } void cachehash_set_evict_cb(cachehash *ch, cachehash_process_cb *cb) { ch->evict_cb = cb; } // is the hashcache full? static inline int eviction_needed(cachehash *ch) { assert(ch); return ch->currsize == ch->maxsize; } // completely evict the LRU object // does not cb to user w/ object static inline void *evict(cachehash *ch) { assert(ch); node_t *last = ch->end; // remove item from judy array int rc; JHSD(rc, ch->judy, last->key, last->keylen); // we should never end up with something in the linked list // that's not in the judy array. assert(rc); // reset linked list node void *retv = last->data; last->data = NULL; free(last->key); last->key = NULL; last->keylen = 0; ch->currsize--; ch->curr_end = ch->end; return retv; } static inline void use(cachehash *ch, node_t *n) { assert(ch); assert(n); //if first node, nothing to do and return if (n == ch->start) { return; } // remove from current spot in linked list node_t *prev = n->prev; n->prev->next = n->next; // if last node then no next, but must update LL if (n->next) { n->next->prev = prev; } else { ch->end = prev; } // front of list n->next = ch->start; ch->start->prev = n; ch->start = n; n->prev = NULL; } static inline node_t *judy_get(cachehash *ch, void *key, size_t keylen) { assert(ch); assert(key); assert(keylen); Word_t *v_; JHSG(v_, ch->judy, key, keylen); if (!v_) { return NULL; } return (node_t *)*v_; } void *cachehash_has(cachehash *ch, const void *key, size_t keylen) { assert(ch); assert(key); assert(keylen); node_t *n = judy_get(ch, (void *)key, keylen); if (n) { return n->data; } else { return NULL; } } void *cachehash_get(cachehash *ch, const void *key, size_t keylen) { assert(ch); assert(key); assert(keylen); node_t *n = judy_get(ch, (void *)key, keylen); if (n) { use(ch, n); return n->data; } else { return NULL; } } void *cachehash_evict_if_full(cachehash *ch) { assert(ch); if (eviction_needed(ch) == EVICTION_UNNEC) { return NULL; } return evict(ch); } void cachehash_put(cachehash *ch, const void *key, size_t keylen, void *value) { assert(ch); assert(key); assert(keylen); void *evicted = cachehash_evict_if_full(ch); if (evicted && ch->evict_cb) { ch->evict_cb(evicted); ch->curr_end = ch->end; } // create new node node_t *n; void *newkey = malloc(keylen); n = ch->curr_end; memcpy(newkey, key, keylen); n->key = newkey; n->keylen = keylen; n->data = value; //n->prev = ch->curr_end->prev; //n->next = ch->curr_end->next; if (ch->curr_end != ch->end) { ch->curr_end = ch->curr_end->next; ch->curr_end->prev = n; } use(ch, n); ch->currsize++; // add to judy array Word_t *v_; JHSI(v_, ch->judy, (void *)key, keylen); // key should not already be in hash table assert(!*v_); *v_ = (Word_t)n; } // print out entire state. void cachehash_debug_dump(cachehash *ch) { printf("Statistics:\n"); printf("\tcurrent size: %lu\n", ch->currsize); printf("\tmaximum size: %lu\n", ch->maxsize); printf("\n"); printf("Linked List:\n"); size_t i = 0; node_t *n = ch->start; do { if (n->key) { printf("\t%lu: %s -> %s\n", i++, (char *)n->key, (char *)n->data); } else { printf("\t%lu: EMPTY\n", i++); } n = n->next; } while (n); } void cachehash_free(cachehash *ch, cachehash_process_cb *cb) { assert(ch); int rc; JHSFA(rc, ch->judy); node_t *n = ch->start; do { if (n->key) { free(n->key); if (cb) { cb(n->data); } } n = n->next; } while (n); free(ch->malloced); free(ch); } void cachehash_iter(cachehash *ch, cachehash_process_cb *cb) { node_t *n = ch->start; do { if (n->key) { cb(n->data); } else { break; } n = n->next; } while (n); }