1/************************************************************************** 2 * 3 * Copyright 2008 VMware, Inc. 4 * All Rights Reserved. 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a 7 * copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sub license, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice (including the 15 * next paragraph) shall be included in all copies or substantial portions 16 * of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 21 * IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR 22 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 * 26 **************************************************************************/ 27 28/** 29 * @file 30 * Improved cache implementation. 31 * 32 * Fixed size array with linear probing on collision and LRU eviction 33 * on full. 34 * 35 * @author Jose Fonseca <jfonseca@vmware.com> 36 */ 37 38 39#include "pipe/p_compiler.h" 40#include "util/u_debug.h" 41 42#include "util/u_math.h" 43#include "util/u_memory.h" 44#include "util/u_cache.h" 45#include "util/u_simple_list.h" 46 47 48struct util_cache_entry 49{ 50 enum { EMPTY = 0, FILLED, DELETED } state; 51 uint32_t hash; 52 53 struct util_cache_entry *next; 54 struct util_cache_entry *prev; 55 56 void *key; 57 void *value; 58 59#ifdef DEBUG 60 unsigned count; 61#endif 62}; 63 64 65struct util_cache 66{ 67 /** Hash function */ 68 uint32_t (*hash)(const void *key); 69 70 /** Compare two keys */ 71 int (*compare)(const void *key1, const void *key2); 72 73 /** Destroy a (key, value) pair */ 74 void (*destroy)(void *key, void *value); 75 76 uint32_t size; 77 78 struct util_cache_entry *entries; 79 80 unsigned count; 81 struct util_cache_entry lru; 82}; 83 84static void 85ensure_sanity(const struct util_cache *cache); 86 87#define CACHE_DEFAULT_ALPHA 2 88 89struct util_cache * 90util_cache_create(uint32_t (*hash)(const void *key), 91 int (*compare)(const void *key1, const void *key2), 92 void (*destroy)(void *key, void *value), 93 uint32_t size) 94{ 95 struct util_cache *cache; 96 97 cache = CALLOC_STRUCT(util_cache); 98 if(!cache) 99 return NULL; 100 101 cache->hash = hash; 102 cache->compare = compare; 103 cache->destroy = destroy; 104 105 make_empty_list(&cache->lru); 106 107 size *= CACHE_DEFAULT_ALPHA; 108 cache->size = size; 109 110 cache->entries = CALLOC(size, sizeof(struct util_cache_entry)); 111 if(!cache->entries) { 112 FREE(cache); 113 return NULL; 114 } 115 116 ensure_sanity(cache); 117 return cache; 118} 119 120 121static struct util_cache_entry * 122util_cache_entry_get(struct util_cache *cache, 123 uint32_t hash, 124 const void *key) 125{ 126 struct util_cache_entry *first_unfilled = NULL; 127 uint32_t index = hash % cache->size; 128 uint32_t probe; 129 130 /* Probe until we find either a matching FILLED entry or an EMPTY 131 * slot (which has never been occupied). 132 * 133 * Deleted or non-matching slots are not indicative of completion 134 * as a previous linear probe for the same key could have continued 135 * past this point. 136 */ 137 for (probe = 0; probe < cache->size; probe++) { 138 uint32_t i = (index + probe) % cache->size; 139 struct util_cache_entry *current = &cache->entries[i]; 140 141 if (current->state == FILLED) { 142 if (current->hash == hash && 143 cache->compare(key, current->key) == 0) 144 return current; 145 } 146 else { 147 if (!first_unfilled) 148 first_unfilled = current; 149 150 if (current->state == EMPTY) 151 return first_unfilled; 152 } 153 } 154 155 return NULL; 156} 157 158static INLINE void 159util_cache_entry_destroy(struct util_cache *cache, 160 struct util_cache_entry *entry) 161{ 162 void *key = entry->key; 163 void *value = entry->value; 164 165 entry->key = NULL; 166 entry->value = NULL; 167 168 if (entry->state == FILLED) { 169 remove_from_list(entry); 170 cache->count--; 171 172 if(cache->destroy) 173 cache->destroy(key, value); 174 175 entry->state = DELETED; 176 } 177} 178 179 180void 181util_cache_set(struct util_cache *cache, 182 void *key, 183 void *value) 184{ 185 struct util_cache_entry *entry; 186 uint32_t hash = cache->hash(key); 187 188 assert(cache); 189 if (!cache) 190 return; 191 192 entry = util_cache_entry_get(cache, hash, key); 193 if (!entry) 194 entry = cache->lru.prev; 195 196 if (cache->count >= cache->size / CACHE_DEFAULT_ALPHA) 197 util_cache_entry_destroy(cache, cache->lru.prev); 198 199 util_cache_entry_destroy(cache, entry); 200 201#ifdef DEBUG 202 ++entry->count; 203#endif 204 205 entry->key = key; 206 entry->hash = hash; 207 entry->value = value; 208 entry->state = FILLED; 209 insert_at_head(&cache->lru, entry); 210 cache->count++; 211 212 ensure_sanity(cache); 213} 214 215 216void * 217util_cache_get(struct util_cache *cache, 218 const void *key) 219{ 220 struct util_cache_entry *entry; 221 uint32_t hash = cache->hash(key); 222 223 assert(cache); 224 if (!cache) 225 return NULL; 226 227 entry = util_cache_entry_get(cache, hash, key); 228 if (!entry) 229 return NULL; 230 231 if (entry->state == FILLED) 232 move_to_head(&cache->lru, entry); 233 234 return entry->value; 235} 236 237 238void 239util_cache_clear(struct util_cache *cache) 240{ 241 uint32_t i; 242 243 assert(cache); 244 if (!cache) 245 return; 246 247 for(i = 0; i < cache->size; ++i) { 248 util_cache_entry_destroy(cache, &cache->entries[i]); 249 cache->entries[i].state = EMPTY; 250 } 251 252 assert(cache->count == 0); 253 assert(is_empty_list(&cache->lru)); 254 ensure_sanity(cache); 255} 256 257 258void 259util_cache_destroy(struct util_cache *cache) 260{ 261 assert(cache); 262 if (!cache) 263 return; 264 265#ifdef DEBUG 266 if(cache->count >= 20*cache->size) { 267 /* Normal approximation of the Poisson distribution */ 268 double mean = (double)cache->count/(double)cache->size; 269 double stddev = sqrt(mean); 270 unsigned i; 271 for(i = 0; i < cache->size; ++i) { 272 double z = fabs(cache->entries[i].count - mean)/stddev; 273 /* This assert should not fail 99.9999998027% of the times, unless 274 * the hash function is a poor one */ 275 assert(z <= 6.0); 276 } 277 } 278#endif 279 280 util_cache_clear(cache); 281 282 FREE(cache->entries); 283 FREE(cache); 284} 285 286 287void 288util_cache_remove(struct util_cache *cache, 289 const void *key) 290{ 291 struct util_cache_entry *entry; 292 uint32_t hash; 293 294 assert(cache); 295 if (!cache) 296 return; 297 298 hash = cache->hash(key); 299 300 entry = util_cache_entry_get(cache, hash, key); 301 if (!entry) 302 return; 303 304 if (entry->state == FILLED) 305 util_cache_entry_destroy(cache, entry); 306 307 ensure_sanity(cache); 308} 309 310 311static void 312ensure_sanity(const struct util_cache *cache) 313{ 314#ifdef DEBUG 315 unsigned i, cnt = 0; 316 317 assert(cache); 318 for (i = 0; i < cache->size; i++) { 319 struct util_cache_entry *header = &cache->entries[i]; 320 321 assert(header); 322 assert(header->state == FILLED || 323 header->state == EMPTY || 324 header->state == DELETED); 325 if (header->state == FILLED) { 326 cnt++; 327 assert(header->hash == cache->hash(header->key)); 328 } 329 } 330 331 assert(cnt == cache->count); 332 assert(cache->size >= cnt); 333 334 if (cache->count == 0) { 335 assert (is_empty_list(&cache->lru)); 336 } 337 else { 338 struct util_cache_entry *header = cache->lru.next; 339 340 assert (header); 341 assert (!is_empty_list(&cache->lru)); 342 343 for (i = 0; i < cache->count; i++) 344 header = header->next; 345 346 assert(header == &cache->lru); 347 } 348#endif 349 350 (void)cache; 351} 352