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 
lv_lru_create(size_t cache_size,size_t average_length,lv_lru_free_t * value_free,lv_lru_free_t * key_free)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 
lv_lru_del(lv_lru_t * cache)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 
lv_lru_set(lv_lru_t * cache,const void * key,size_t key_length,void * value,size_t value_length)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 
lv_lru_get(lv_lru_t * cache,const void * key,size_t key_size,void ** value)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 
lv_lru_remove(lv_lru_t * cache,const void * key,size_t key_size)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 
lv_lru_hash(lv_lru_t * cache,const void * key,uint32_t key_length)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 
lv_lru_cmp_keys(lv_lru_item_t * item,const void * key,uint32_t key_length)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 
lv_lru_remove_item(lv_lru_t * cache,lv_lru_item_t * prev,lv_lru_item_t * item,uint32_t hash_index)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 
lv_lru_remove_lru_item(lv_lru_t * cache)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 
lv_lru_pop_or_create_item(lv_lru_t * cache)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 }
352