acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

lv_lru.c (9760B)

      1 /**
      2  * @file lv_lru.c
      3  *
      4  * @see https://github.com/willcannings/C-LRU-Cache
      5  */
      6 
      7 /*********************
      8  *      INCLUDES
      9  *********************/
     10 
     11 #include "lv_lru.h"
     12 #include "lv_math.h"
     13 #include "lv_mem.h"
     14 #include "lv_assert.h"
     15 #include "lv_log.h"
     16 
     17 /*********************
     18  *      DEFINES
     19  *********************/
     20 
     21 /**********************
     22  *      TYPEDEFS
     23  **********************/
     24 
     25 struct _lv_lru_item_t {
     26     void * value;
     27     void * key;
     28     size_t value_length;
     29     size_t key_length;
     30     uint64_t access_count;
     31     struct _lv_lru_item_t * next;
     32 };
     33 
     34 /**********************
     35  *  STATIC PROTOTYPES
     36  **********************/
     37 
     38 /**
     39  * MurmurHash2
     40  * @author Austin Appleby
     41  * @see http://sites.google.com/site/murmurhash/
     42  */
     43 static uint32_t lv_lru_hash(lv_lru_t * cache, const void * key, uint32_t key_length);
     44 
     45 /** compare a key against an existing item's key */
     46 static int lv_lru_cmp_keys(lv_lru_item_t * item, const void * key, uint32_t key_length);
     47 
     48 /** remove an item and push it to the free items queue */
     49 static void lv_lru_remove_item(lv_lru_t * cache, lv_lru_item_t * prev, lv_lru_item_t * item, uint32_t hash_index);
     50 
     51 /**
     52  * remove the least recently used item
     53  *
     54  * @todo we can optimise this by finding the n lru items, where n = required_space / average_length
     55  */
     56 static void lv_lru_remove_lru_item(lv_lru_t * cache);
     57 
     58 /** pop an existing item off the free queue, or create a new one */
     59 static lv_lru_item_t * lv_lru_pop_or_create_item(lv_lru_t * cache);
     60 
     61 /**********************
     62  *  STATIC VARIABLES
     63  **********************/
     64 
     65 /**********************
     66  *      MACROS
     67  **********************/
     68 
     69 /* error helpers */
     70 #define error_for(conditions, error)  if(conditions) {return error;}
     71 #define test_for_missing_cache()      error_for(!cache, LV_LRU_MISSING_CACHE)
     72 #define test_for_missing_key()        error_for(!key, LV_LRU_MISSING_KEY)
     73 #define test_for_missing_value()      error_for(!value || value_length == 0, LV_LRU_MISSING_VALUE)
     74 #define test_for_value_too_large()    error_for(value_length > cache->total_memory, LV_LRU_VALUE_TOO_LARGE)
     75 
     76 /**********************
     77  *   GLOBAL FUNCTIONS
     78  **********************/
     79 
     80 lv_lru_t * lv_lru_create(size_t cache_size, size_t average_length, lv_lru_free_t * value_free,
     81                          lv_lru_free_t * key_free)
     82 {
     83     // create the cache
     84     lv_lru_t * cache = (lv_lru_t *) lv_mem_alloc(sizeof(lv_lru_t));
     85     lv_memset_00(cache, sizeof(lv_lru_t));
     86     if(!cache) {
     87         LV_LOG_WARN("LRU Cache unable to create cache object");
     88         return NULL;
     89     }
     90     cache->hash_table_size = cache_size / average_length;
     91     cache->average_item_length = average_length;
     92     cache->free_memory = cache_size;
     93     cache->total_memory = cache_size;
     94     cache->seed = lv_rand(1, UINT32_MAX);
     95     cache->value_free = value_free ? value_free : lv_mem_free;
     96     cache->key_free = key_free ? key_free : lv_mem_free;
     97 
     98     // size the hash table to a guestimate of the number of slots required (assuming a perfect hash)
     99     cache->items = (lv_lru_item_t **) lv_mem_alloc(sizeof(lv_lru_item_t *) * cache->hash_table_size);
    100     lv_memset_00(cache->items, sizeof(lv_lru_item_t *) * cache->hash_table_size);
    101     if(!cache->items) {
    102         LV_LOG_WARN("LRU Cache unable to create cache hash table");
    103         lv_mem_free(cache);
    104         return NULL;
    105     }
    106     return cache;
    107 }
    108 
    109 
    110 void lv_lru_del(lv_lru_t * cache)
    111 {
    112     LV_ASSERT_NULL(cache);
    113 
    114     // free each of the cached items, and the hash table
    115     lv_lru_item_t * item = NULL, *next = NULL;
    116     uint32_t i = 0;
    117     if(cache->items) {
    118         for(; i < cache->hash_table_size; i++) {
    119             item = cache->items[i];
    120             while(item) {
    121                 next = (lv_lru_item_t *) item->next;
    122                 cache->value_free(item->value);
    123                 cache->key_free(item->key);
    124                 cache->free_memory += item->value_length;
    125                 lv_mem_free(item);
    126                 item = next;
    127             }
    128         }
    129         lv_mem_free(cache->items);
    130     }
    131 
    132     if(cache->free_items) {
    133         item = cache->free_items;
    134         while(item) {
    135             next = (lv_lru_item_t *) item->next;
    136             lv_mem_free(item);
    137             item = next;
    138         }
    139     }
    140 
    141     // free the cache
    142     lv_mem_free(cache);
    143 }
    144 
    145 
    146 lv_lru_res_t lv_lru_set(lv_lru_t * cache, const void * key, size_t key_length, void * value, size_t value_length)
    147 {
    148     test_for_missing_cache();
    149     test_for_missing_key();
    150     test_for_missing_value();
    151     test_for_value_too_large();
    152 
    153     // see if the key already exists
    154     uint32_t hash_index = lv_lru_hash(cache, key, key_length);
    155     size_t required = 0;
    156     lv_lru_item_t * item = NULL, *prev = NULL;
    157     item = cache->items[hash_index];
    158 
    159     while(item && lv_lru_cmp_keys(item, key, key_length)) {
    160         prev = item;
    161         item = (lv_lru_item_t *) item->next;
    162     }
    163 
    164     if(item) {
    165         // update the value and value_lengths
    166         required = (size_t)(value_length - item->value_length);
    167         cache->value_free(item->value);
    168         item->value = value;
    169         item->value_length = value_length;
    170 
    171     }
    172     else {
    173         // insert a new item
    174         item = lv_lru_pop_or_create_item(cache);
    175         item->value = value;
    176         item->key = lv_mem_alloc(key_length);
    177         memcpy(item->key, key, key_length);
    178         item->value_length = value_length;
    179         item->key_length = key_length;
    180         required = (size_t) value_length;
    181 
    182         if(prev)
    183             prev->next = item;
    184         else
    185             cache->items[hash_index] = item;
    186     }
    187     item->access_count = ++cache->access_count;
    188 
    189     // remove as many items as necessary to free enough space
    190     if(required > 0 && (size_t) required > cache->free_memory) {
    191         while(cache->free_memory < (size_t) required)
    192             lv_lru_remove_lru_item(cache);
    193     }
    194     cache->free_memory -= required;
    195     return LV_LRU_OK;
    196 }
    197 
    198 
    199 lv_lru_res_t lv_lru_get(lv_lru_t * cache, const void * key, size_t key_size, void ** value)
    200 {
    201     test_for_missing_cache();
    202     test_for_missing_key();
    203 
    204     // loop until we find the item, or hit the end of a chain
    205     uint32_t hash_index = lv_lru_hash(cache, key, key_size);
    206     lv_lru_item_t * item = cache->items[hash_index];
    207 
    208     while(item && lv_lru_cmp_keys(item, key, key_size))
    209         item = (lv_lru_item_t *) item->next;
    210 
    211     if(item) {
    212         *value = item->value;
    213         item->access_count = ++cache->access_count;
    214     }
    215     else {
    216         *value = NULL;
    217     }
    218 
    219     return LV_LRU_OK;
    220 }
    221 
    222 lv_lru_res_t lv_lru_remove(lv_lru_t * cache, const void * key, size_t key_size)
    223 {
    224     test_for_missing_cache();
    225     test_for_missing_key();
    226 
    227     // loop until we find the item, or hit the end of a chain
    228     lv_lru_item_t * item = NULL, *prev = NULL;
    229     uint32_t hash_index = lv_lru_hash(cache, key, key_size);
    230     item = cache->items[hash_index];
    231 
    232     while(item && lv_lru_cmp_keys(item, key, key_size)) {
    233         prev = item;
    234         item = (lv_lru_item_t *) item->next;
    235     }
    236 
    237     if(item) {
    238         lv_lru_remove_item(cache, prev, item, hash_index);
    239     }
    240 
    241     return LV_LRU_OK;
    242 }
    243 
    244 /**********************
    245  *   STATIC FUNCTIONS
    246  **********************/
    247 
    248 static uint32_t lv_lru_hash(lv_lru_t * cache, const void * key, uint32_t key_length)
    249 {
    250     uint32_t m = 0x5bd1e995;
    251     uint32_t r = 24;
    252     uint32_t h = cache->seed ^ key_length;
    253     char * data = (char *) key;
    254 
    255     while(key_length >= 4) {
    256         uint32_t k = *(uint32_t *) data;
    257         k *= m;
    258         k ^= k >> r;
    259         k *= m;
    260         h *= m;
    261         h ^= k;
    262         data += 4;
    263         key_length -= 4;
    264     }
    265 
    266     if(key_length >= 3) {
    267         h ^= data[2] << 16;
    268     }
    269     if(key_length >= 2) {
    270         h ^= data[1] << 8;
    271     }
    272     if(key_length >= 1) {
    273         h ^= data[0];
    274         h *= m;
    275     }
    276 
    277     h ^= h >> 13;
    278     h *= m;
    279     h ^= h >> 15;
    280     return h % cache->hash_table_size;
    281 }
    282 
    283 static int lv_lru_cmp_keys(lv_lru_item_t * item, const void * key, uint32_t key_length)
    284 {
    285     if(key_length != item->key_length)
    286         return 1;
    287     else
    288         return memcmp(key, item->key, key_length);
    289 }
    290 
    291 static void lv_lru_remove_item(lv_lru_t * cache, lv_lru_item_t * prev, lv_lru_item_t * item, uint32_t hash_index)
    292 {
    293     if(prev)
    294         prev->next = item->next;
    295     else
    296         cache->items[hash_index] = (lv_lru_item_t *) item->next;
    297 
    298     // free memory and update the free memory counter
    299     cache->free_memory += item->value_length;
    300     cache->value_free(item->value);
    301     cache->key_free(item->key);
    302 
    303     // push the item to the free items queue
    304     lv_memset_00(item, sizeof(lv_lru_item_t));
    305     item->next = cache->free_items;
    306     cache->free_items = item;
    307 }
    308 
    309 static void lv_lru_remove_lru_item(lv_lru_t * cache)
    310 {
    311     lv_lru_item_t * min_item = NULL, *min_prev = NULL;
    312     lv_lru_item_t * item = NULL, *prev = NULL;
    313     uint32_t i = 0, min_index = -1;
    314     uint64_t min_access_count = -1;
    315 
    316     for(; i < cache->hash_table_size; i++) {
    317         item = cache->items[i];
    318         prev = NULL;
    319 
    320         while(item) {
    321             if(item->access_count < min_access_count || (int64_t) min_access_count == -1) {
    322                 min_access_count = item->access_count;
    323                 min_item = item;
    324                 min_prev = prev;
    325                 min_index = i;
    326             }
    327             prev = item;
    328             item = item->next;
    329         }
    330     }
    331 
    332     if(min_item)
    333         lv_lru_remove_item(cache, min_prev, min_item, min_index);
    334 }
    335 
    336 static lv_lru_item_t * lv_lru_pop_or_create_item(lv_lru_t * cache)
    337 {
    338     lv_lru_item_t * item = NULL;
    339 
    340     if(cache->free_items) {
    341         item = cache->free_items;
    342         cache->free_items = item->next;
    343         lv_memset_00(item, sizeof(lv_lru_item_t));
    344     }
    345     else {
    346         item = (lv_lru_item_t *) lv_mem_alloc(sizeof(lv_lru_item_t));
    347         lv_memset_00(item, sizeof(lv_lru_item_t));
    348     }
    349 
    350     return item;
    351 }