Home | History | Annotate | Download | only in util
      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 (at) 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/simple_list.h"
     46 
     47 
     48 struct 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 
     65 struct 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    /** Max entries in the cache */
     77    uint32_t size;
     78 
     79    /** Array [size] of entries */
     80    struct util_cache_entry *entries;
     81 
     82    /** Number of entries in the cache */
     83    unsigned count;
     84 
     85    /** Head of list, sorted from Least-recently used to Most-recently used */
     86    struct util_cache_entry lru;
     87 };
     88 
     89 
     90 static void
     91 ensure_sanity(const struct util_cache *cache);
     92 
     93 #define CACHE_DEFAULT_ALPHA 2
     94 
     95 /**
     96  * Create a new cache with 'size' entries.  Also provide functions for
     97  * hashing keys, comparing keys and destroying (key,value) pairs.
     98  */
     99 struct util_cache *
    100 util_cache_create(uint32_t (*hash)(const void *key),
    101                   int (*compare)(const void *key1, const void *key2),
    102                   void (*destroy)(void *key, void *value),
    103                   uint32_t size)
    104 {
    105    struct util_cache *cache;
    106 
    107    cache = CALLOC_STRUCT(util_cache);
    108    if (!cache)
    109       return NULL;
    110 
    111    cache->hash = hash;
    112    cache->compare = compare;
    113    cache->destroy = destroy;
    114 
    115    make_empty_list(&cache->lru);
    116 
    117    size *= CACHE_DEFAULT_ALPHA;
    118    cache->size = size;
    119 
    120    cache->entries = CALLOC(size, sizeof(struct util_cache_entry));
    121    if (!cache->entries) {
    122       FREE(cache);
    123       return NULL;
    124    }
    125 
    126    ensure_sanity(cache);
    127    return cache;
    128 }
    129 
    130 
    131 /**
    132  * Try to find a cache entry, given the key and hash of the key.
    133  */
    134 static struct util_cache_entry *
    135 util_cache_entry_get(struct util_cache *cache,
    136                      uint32_t hash,
    137                      const void *key)
    138 {
    139    struct util_cache_entry *first_unfilled = NULL;
    140    uint32_t index = hash % cache->size;
    141    uint32_t probe;
    142 
    143    /* Probe until we find either a matching FILLED entry or an EMPTY
    144     * slot (which has never been occupied).
    145     *
    146     * Deleted or non-matching slots are not indicative of completion
    147     * as a previous linear probe for the same key could have continued
    148     * past this point.
    149     */
    150    for (probe = 0; probe < cache->size; probe++) {
    151       uint32_t i = (index + probe) % cache->size;
    152       struct util_cache_entry *current = &cache->entries[i];
    153 
    154       if (current->state == FILLED) {
    155          if (current->hash == hash &&
    156              cache->compare(key, current->key) == 0)
    157             return current;
    158       }
    159       else {
    160          if (!first_unfilled)
    161             first_unfilled = current;
    162 
    163          if (current->state == EMPTY)
    164             return first_unfilled;
    165       }
    166    }
    167 
    168    return NULL;
    169 }
    170 
    171 static inline void
    172 util_cache_entry_destroy(struct util_cache *cache,
    173                          struct util_cache_entry *entry)
    174 {
    175    void *key = entry->key;
    176    void *value = entry->value;
    177 
    178    entry->key = NULL;
    179    entry->value = NULL;
    180 
    181    if (entry->state == FILLED) {
    182       remove_from_list(entry);
    183       cache->count--;
    184 
    185       if (cache->destroy)
    186          cache->destroy(key, value);
    187 
    188       entry->state = DELETED;
    189    }
    190 }
    191 
    192 
    193 /**
    194  * Insert an entry in the cache, given the key and value.
    195  */
    196 void
    197 util_cache_set(struct util_cache *cache,
    198                void *key,
    199                void *value)
    200 {
    201    struct util_cache_entry *entry;
    202    uint32_t hash;
    203 
    204    assert(cache);
    205    if (!cache)
    206       return;
    207    hash = cache->hash(key);
    208    entry = util_cache_entry_get(cache, hash, key);
    209    if (!entry)
    210       entry = cache->lru.prev;
    211 
    212    if (cache->count >= cache->size / CACHE_DEFAULT_ALPHA)
    213       util_cache_entry_destroy(cache, cache->lru.prev);
    214 
    215    util_cache_entry_destroy(cache, entry);
    216 
    217 #ifdef DEBUG
    218    ++entry->count;
    219 #endif
    220 
    221    entry->key = key;
    222    entry->hash = hash;
    223    entry->value = value;
    224    entry->state = FILLED;
    225    insert_at_head(&cache->lru, entry);
    226    cache->count++;
    227 
    228    ensure_sanity(cache);
    229 }
    230 
    231 
    232 /**
    233  * Search the cache for an entry with the given key.  Return the corresponding
    234  * value or NULL if not found.
    235  */
    236 void *
    237 util_cache_get(struct util_cache *cache,
    238                const void *key)
    239 {
    240    struct util_cache_entry *entry;
    241    uint32_t hash;
    242 
    243    assert(cache);
    244    if (!cache)
    245       return NULL;
    246    hash = cache->hash(key);
    247    entry = util_cache_entry_get(cache, hash, key);
    248    if (!entry)
    249       return NULL;
    250 
    251    if (entry->state == FILLED)
    252       move_to_head(&cache->lru, entry);
    253 
    254    return entry->value;
    255 }
    256 
    257 
    258 /**
    259  * Remove all entries from the cache.  The 'destroy' function will be called
    260  * for each entry's (key, value).
    261  */
    262 void
    263 util_cache_clear(struct util_cache *cache)
    264 {
    265    uint32_t i;
    266 
    267    assert(cache);
    268    if (!cache)
    269       return;
    270 
    271    for (i = 0; i < cache->size; ++i) {
    272       util_cache_entry_destroy(cache, &cache->entries[i]);
    273       cache->entries[i].state = EMPTY;
    274    }
    275 
    276    assert(cache->count == 0);
    277    assert(is_empty_list(&cache->lru));
    278    ensure_sanity(cache);
    279 }
    280 
    281 
    282 /**
    283  * Destroy the cache and all entries.
    284  */
    285 void
    286 util_cache_destroy(struct util_cache *cache)
    287 {
    288    assert(cache);
    289    if (!cache)
    290       return;
    291 
    292 #ifdef DEBUG
    293    if (cache->count >= 20*cache->size) {
    294       /* Normal approximation of the Poisson distribution */
    295       double mean = (double)cache->count/(double)cache->size;
    296       double stddev = sqrt(mean);
    297       unsigned i;
    298       for (i = 0; i < cache->size; ++i) {
    299          double z = fabs(cache->entries[i].count - mean)/stddev;
    300          /* This assert should not fail 99.9999998027% of the times, unless
    301           * the hash function is a poor one */
    302          assert(z <= 6.0);
    303       }
    304    }
    305 #endif
    306 
    307    util_cache_clear(cache);
    308 
    309    FREE(cache->entries);
    310    FREE(cache);
    311 }
    312 
    313 
    314 /**
    315  * Remove the cache entry which matches the given key.
    316  */
    317 void
    318 util_cache_remove(struct util_cache *cache,
    319                   const void *key)
    320 {
    321    struct util_cache_entry *entry;
    322    uint32_t hash;
    323 
    324    assert(cache);
    325    if (!cache)
    326       return;
    327 
    328    hash = cache->hash(key);
    329 
    330    entry = util_cache_entry_get(cache, hash, key);
    331    if (!entry)
    332       return;
    333 
    334    if (entry->state == FILLED)
    335       util_cache_entry_destroy(cache, entry);
    336 
    337    ensure_sanity(cache);
    338 }
    339 
    340 
    341 static void
    342 ensure_sanity(const struct util_cache *cache)
    343 {
    344 #ifdef DEBUG
    345    unsigned i, cnt = 0;
    346 
    347    assert(cache);
    348    for (i = 0; i < cache->size; i++) {
    349       struct util_cache_entry *header = &cache->entries[i];
    350 
    351       assert(header);
    352       assert(header->state == FILLED ||
    353              header->state == EMPTY ||
    354              header->state == DELETED);
    355       if (header->state == FILLED) {
    356          cnt++;
    357          assert(header->hash == cache->hash(header->key));
    358       }
    359    }
    360 
    361    assert(cnt == cache->count);
    362    assert(cache->size >= cnt);
    363 
    364    if (cache->count == 0) {
    365       assert (is_empty_list(&cache->lru));
    366    }
    367    else {
    368       struct util_cache_entry *header = cache->lru.next;
    369 
    370       assert (header);
    371       assert (!is_empty_list(&cache->lru));
    372 
    373       for (i = 0; i < cache->count; i++)
    374          header = header->next;
    375 
    376       assert(header == &cache->lru);
    377    }
    378 #endif
    379 
    380    (void)cache;
    381 }
    382