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