Path: blob/21.2-virgl/src/gallium/auxiliary/util/u_cache.c
4561 views
/**************************************************************************1*2* Copyright 2008 VMware, Inc.3* All Rights Reserved.4*5* Permission is hereby granted, free of charge, to any person obtaining a6* copy of this software and associated documentation files (the7* "Software"), to deal in the Software without restriction, including8* without limitation the rights to use, copy, modify, merge, publish,9* distribute, sub license, and/or sell copies of the Software, and to10* permit persons to whom the Software is furnished to do so, subject to11* the following conditions:12*13* The above copyright notice and this permission notice (including the14* next paragraph) shall be included in all copies or substantial portions15* of the Software.16*17* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS18* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF19* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.20* IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR21* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,22* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE23* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.24*25**************************************************************************/2627/**28* @file29* Improved cache implementation.30*31* Fixed size array with linear probing on collision and LRU eviction32* on full.33*34* @author Jose Fonseca <[email protected]>35*/363738#include "pipe/p_compiler.h"39#include "util/u_debug.h"4041#include "util/u_math.h"42#include "util/u_memory.h"43#include "util/u_cache.h"44#include "util/simple_list.h"454647struct util_cache_entry48{49enum { EMPTY = 0, FILLED, DELETED } state;50uint32_t hash;5152struct util_cache_entry *next;53struct util_cache_entry *prev;5455void *key;56void *value;5758#ifdef DEBUG59unsigned count;60#endif61};626364struct util_cache65{66/** Hash function */67uint32_t (*hash)(const void *key);6869/** Compare two keys */70int (*compare)(const void *key1, const void *key2);7172/** Destroy a (key, value) pair */73void (*destroy)(void *key, void *value);7475/** Max entries in the cache */76uint32_t size;7778/** Array [size] of entries */79struct util_cache_entry *entries;8081/** Number of entries in the cache */82unsigned count;8384/** Head of list, sorted from Least-recently used to Most-recently used */85struct util_cache_entry lru;86};878889static void90ensure_sanity(const struct util_cache *cache);9192#define CACHE_DEFAULT_ALPHA 29394/**95* Create a new cache with 'size' entries. Also provide functions for96* hashing keys, comparing keys and destroying (key,value) pairs.97*/98struct util_cache *99util_cache_create(uint32_t (*hash)(const void *key),100int (*compare)(const void *key1, const void *key2),101void (*destroy)(void *key, void *value),102uint32_t size)103{104struct util_cache *cache;105106cache = CALLOC_STRUCT(util_cache);107if (!cache)108return NULL;109110cache->hash = hash;111cache->compare = compare;112cache->destroy = destroy;113114make_empty_list(&cache->lru);115116size *= CACHE_DEFAULT_ALPHA;117cache->size = size;118119cache->entries = CALLOC(size, sizeof(struct util_cache_entry));120if (!cache->entries) {121FREE(cache);122return NULL;123}124125ensure_sanity(cache);126return cache;127}128129130/**131* Try to find a cache entry, given the key and hash of the key.132*/133static struct util_cache_entry *134util_cache_entry_get(struct util_cache *cache,135uint32_t hash,136const void *key)137{138struct util_cache_entry *first_unfilled = NULL;139uint32_t index = hash % cache->size;140uint32_t probe;141142/* Probe until we find either a matching FILLED entry or an EMPTY143* slot (which has never been occupied).144*145* Deleted or non-matching slots are not indicative of completion146* as a previous linear probe for the same key could have continued147* past this point.148*/149for (probe = 0; probe < cache->size; probe++) {150uint32_t i = (index + probe) % cache->size;151struct util_cache_entry *current = &cache->entries[i];152153if (current->state == FILLED) {154if (current->hash == hash &&155cache->compare(key, current->key) == 0)156return current;157}158else {159if (!first_unfilled)160first_unfilled = current;161162if (current->state == EMPTY)163return first_unfilled;164}165}166167return NULL;168}169170static inline void171util_cache_entry_destroy(struct util_cache *cache,172struct util_cache_entry *entry)173{174void *key = entry->key;175void *value = entry->value;176177entry->key = NULL;178entry->value = NULL;179180if (entry->state == FILLED) {181remove_from_list(entry);182cache->count--;183184if (cache->destroy)185cache->destroy(key, value);186187entry->state = DELETED;188}189}190191192/**193* Insert an entry in the cache, given the key and value.194*/195void196util_cache_set(struct util_cache *cache,197void *key,198void *value)199{200struct util_cache_entry *entry;201uint32_t hash;202203assert(cache);204if (!cache)205return;206hash = cache->hash(key);207entry = util_cache_entry_get(cache, hash, key);208if (!entry)209entry = cache->lru.prev;210211if (cache->count >= cache->size / CACHE_DEFAULT_ALPHA)212util_cache_entry_destroy(cache, cache->lru.prev);213214util_cache_entry_destroy(cache, entry);215216#ifdef DEBUG217++entry->count;218#endif219220entry->key = key;221entry->hash = hash;222entry->value = value;223entry->state = FILLED;224insert_at_head(&cache->lru, entry);225cache->count++;226227ensure_sanity(cache);228}229230231/**232* Search the cache for an entry with the given key. Return the corresponding233* value or NULL if not found.234*/235void *236util_cache_get(struct util_cache *cache,237const void *key)238{239struct util_cache_entry *entry;240uint32_t hash;241242assert(cache);243if (!cache)244return NULL;245hash = cache->hash(key);246entry = util_cache_entry_get(cache, hash, key);247if (!entry)248return NULL;249250if (entry->state == FILLED)251move_to_head(&cache->lru, entry);252253return entry->value;254}255256257/**258* Remove all entries from the cache. The 'destroy' function will be called259* for each entry's (key, value).260*/261void262util_cache_clear(struct util_cache *cache)263{264uint32_t i;265266assert(cache);267if (!cache)268return;269270for (i = 0; i < cache->size; ++i) {271util_cache_entry_destroy(cache, &cache->entries[i]);272cache->entries[i].state = EMPTY;273}274275assert(cache->count == 0);276assert(is_empty_list(&cache->lru));277ensure_sanity(cache);278}279280281/**282* Destroy the cache and all entries.283*/284void285util_cache_destroy(struct util_cache *cache)286{287assert(cache);288if (!cache)289return;290291#ifdef DEBUG292if (cache->count >= 20*cache->size) {293/* Normal approximation of the Poisson distribution */294double mean = (double)cache->count/(double)cache->size;295double stddev = sqrt(mean);296unsigned i;297for (i = 0; i < cache->size; ++i) {298double z = fabs(cache->entries[i].count - mean)/stddev;299/* This assert should not fail 99.9999998027% of the times, unless300* the hash function is a poor one */301assert(z <= 6.0);302}303}304#endif305306util_cache_clear(cache);307308FREE(cache->entries);309FREE(cache);310}311312313/**314* Remove the cache entry which matches the given key.315*/316void317util_cache_remove(struct util_cache *cache,318const void *key)319{320struct util_cache_entry *entry;321uint32_t hash;322323assert(cache);324if (!cache)325return;326327hash = cache->hash(key);328329entry = util_cache_entry_get(cache, hash, key);330if (!entry)331return;332333if (entry->state == FILLED)334util_cache_entry_destroy(cache, entry);335336ensure_sanity(cache);337}338339340static void341ensure_sanity(const struct util_cache *cache)342{343#ifdef DEBUG344unsigned i, cnt = 0;345346assert(cache);347for (i = 0; i < cache->size; i++) {348struct util_cache_entry *header = &cache->entries[i];349350assert(header);351assert(header->state == FILLED ||352header->state == EMPTY ||353header->state == DELETED);354if (header->state == FILLED) {355cnt++;356assert(header->hash == cache->hash(header->key));357}358}359360assert(cnt == cache->count);361assert(cache->size >= cnt);362363if (cache->count == 0) {364assert (is_empty_list(&cache->lru));365}366else {367struct util_cache_entry *header = cache->lru.next;368369assert (header);370assert (!is_empty_list(&cache->lru));371372for (i = 0; i < cache->count; i++)373header = header->next;374375assert(header == &cache->lru);376}377#endif378379(void)cache;380}381382383