295 lines
7.7 KiB
C
295 lines
7.7 KiB
C
/*
|
|
* Copyright (C) 2015 Willy Tarreau <w@1wt.eu>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
* OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <import/lru.h>
|
|
|
|
/* Minimal list manipulation macros for lru64_list */
|
|
#define LIST_ADD(lh, el) ({ (el)->n = (lh)->n; (el)->n->p = (lh)->n = (el); (el)->p = (lh); })
|
|
#define LIST_DEL(el) ({ (el)->n->p = (el)->p; (el)->p->n = (el)->n; })
|
|
|
|
|
|
/* Lookup key <key> in LRU cache <lru> for use with domain <domain> whose data's
|
|
* current version is <revision>. It differs from lru64_get as it does not
|
|
* create missing keys. The function returns NULL if an error or a cache miss
|
|
* occurs. */
|
|
struct lru64 *lru64_lookup(unsigned long long key, struct lru64_head *lru,
|
|
void *domain, unsigned long long revision)
|
|
{
|
|
struct eb64_node *node;
|
|
struct lru64 *elem;
|
|
|
|
node = __eb64_lookup(&lru->keys, key);
|
|
elem = container_of(node, typeof(*elem), node);
|
|
if (elem) {
|
|
/* Existing entry found, check validity then move it at the
|
|
* head of the LRU list.
|
|
*/
|
|
if (elem->domain == domain && elem->revision == revision) {
|
|
LIST_DEL(&elem->lru);
|
|
LIST_ADD(&lru->list, &elem->lru);
|
|
return elem;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Get key <key> from LRU cache <lru> for use with domain <domain> whose data's
|
|
* current revision is <revision>. If the key doesn't exist it's first created
|
|
* with ->domain = NULL. The caller detects this situation by checking ->domain
|
|
* and must perform the operation to be cached then call lru64_commit() to
|
|
* complete the operation. A lock (mutex or spinlock) may be added around the
|
|
* function to permit use in a multi-threaded environment. The function may
|
|
* return NULL upon memory allocation failure.
|
|
*/
|
|
struct lru64 *lru64_get(unsigned long long key, struct lru64_head *lru,
|
|
void *domain, unsigned long long revision)
|
|
{
|
|
struct eb64_node *node;
|
|
struct lru64 *elem;
|
|
|
|
if (!lru->spare) {
|
|
if (!lru->cache_size)
|
|
return NULL;
|
|
lru->spare = malloc(sizeof(*lru->spare));
|
|
if (!lru->spare)
|
|
return NULL;
|
|
lru->spare->domain = NULL;
|
|
}
|
|
|
|
/* Lookup or insert */
|
|
lru->spare->node.key = key;
|
|
node = __eb64_insert(&lru->keys, &lru->spare->node);
|
|
elem = container_of(node, typeof(*elem), node);
|
|
|
|
if (elem != lru->spare) {
|
|
/* Existing entry found, check validity then move it at the
|
|
* head of the LRU list.
|
|
*/
|
|
if (elem->domain == domain && elem->revision == revision) {
|
|
LIST_DEL(&elem->lru);
|
|
LIST_ADD(&lru->list, &elem->lru);
|
|
return elem;
|
|
}
|
|
|
|
if (!elem->domain)
|
|
return NULL; // currently locked
|
|
|
|
/* recycle this entry */
|
|
LIST_DEL(&elem->lru);
|
|
}
|
|
else {
|
|
/* New entry inserted, initialize and move to the head of the
|
|
* LRU list, and lock it until commit.
|
|
*/
|
|
lru->cache_usage++;
|
|
lru->spare = NULL; // used, need a new one next time
|
|
}
|
|
|
|
elem->domain = NULL;
|
|
LIST_ADD(&lru->list, &elem->lru);
|
|
|
|
if (lru->cache_usage > lru->cache_size) {
|
|
/* try to kill oldest entry */
|
|
struct lru64 *old;
|
|
|
|
old = container_of(lru->list.p, typeof(*old), lru);
|
|
if (old->domain) {
|
|
/* not locked */
|
|
LIST_DEL(&old->lru);
|
|
__eb64_delete(&old->node);
|
|
if (old->data && old->free)
|
|
old->free(old->data);
|
|
if (!lru->spare)
|
|
lru->spare = old;
|
|
else {
|
|
free(old);
|
|
}
|
|
lru->cache_usage--;
|
|
}
|
|
}
|
|
return elem;
|
|
}
|
|
|
|
/* Commit element <elem> with data <data>, domain <domain> and revision
|
|
* <revision>. <elem> is checked for NULL so that it's possible to call it
|
|
* with the result from a call to lru64_get(). The caller might lock it using a
|
|
* spinlock or mutex shared with the one around lru64_get().
|
|
*/
|
|
void lru64_commit(struct lru64 *elem, void *data, void *domain,
|
|
unsigned long long revision, void (*free)(void *))
|
|
{
|
|
if (!elem)
|
|
return;
|
|
|
|
elem->data = data;
|
|
elem->revision = revision;
|
|
elem->domain = domain;
|
|
elem->free = free;
|
|
}
|
|
|
|
/* Create a new LRU cache of <size> entries. Returns the new cache or NULL in
|
|
* case of allocation failure.
|
|
*/
|
|
struct lru64_head *lru64_new(int size)
|
|
{
|
|
struct lru64_head *lru;
|
|
|
|
lru = malloc(sizeof(*lru));
|
|
if (lru) {
|
|
lru->list.p = lru->list.n = &lru->list;
|
|
lru->keys = EB_ROOT_UNIQUE;
|
|
lru->spare = NULL;
|
|
lru->cache_size = size;
|
|
lru->cache_usage = 0;
|
|
}
|
|
return lru;
|
|
}
|
|
|
|
/* Tries to destroy the LRU cache <lru>. Returns the number of locked entries
|
|
* that prevent it from being destroyed, or zero meaning everything was done.
|
|
*/
|
|
int lru64_destroy(struct lru64_head *lru)
|
|
{
|
|
struct lru64 *elem, *next;
|
|
|
|
if (!lru)
|
|
return 0;
|
|
|
|
elem = container_of(lru->list.p, typeof(*elem), lru);
|
|
while (&elem->lru != &lru->list) {
|
|
next = container_of(elem->lru.p, typeof(*next), lru);
|
|
if (elem->domain) {
|
|
/* not locked */
|
|
LIST_DEL(&elem->lru);
|
|
eb64_delete(&elem->node);
|
|
if (elem->data && elem->free)
|
|
elem->free(elem->data);
|
|
free(elem);
|
|
lru->cache_usage--;
|
|
lru->cache_size--;
|
|
}
|
|
elem = next;
|
|
}
|
|
|
|
if (lru->cache_usage)
|
|
return lru->cache_usage;
|
|
|
|
free(lru);
|
|
return 0;
|
|
}
|
|
|
|
/* kill the <nb> least used entries from the <lru> cache */
|
|
void lru64_kill_oldest(struct lru64_head *lru, unsigned long int nb)
|
|
{
|
|
struct lru64 *elem, *next;
|
|
|
|
for (elem = container_of(lru->list.p, typeof(*elem), lru);
|
|
nb && (&elem->lru != &lru->list);
|
|
elem = next) {
|
|
next = container_of(elem->lru.p, typeof(*next), lru);
|
|
if (!elem->domain)
|
|
continue; /* locked entry */
|
|
|
|
LIST_DEL(&elem->lru);
|
|
eb64_delete(&elem->node);
|
|
if (elem->data && elem->free)
|
|
elem->free(elem->data);
|
|
if (!lru->spare)
|
|
lru->spare = elem;
|
|
else
|
|
free(elem);
|
|
lru->cache_usage--;
|
|
nb--;
|
|
}
|
|
}
|
|
|
|
/* The code below is just for validation and performance testing. It's an
|
|
* example of a function taking some time to return results that could be
|
|
* cached.
|
|
*/
|
|
#ifdef STANDALONE
|
|
|
|
#include <stdio.h>
|
|
|
|
static unsigned int misses;
|
|
|
|
static unsigned long long sum(unsigned long long x)
|
|
{
|
|
#ifndef TEST_LRU_FAST_OPERATION
|
|
if (x < 1)
|
|
return 0;
|
|
return x + sum(x * 99 / 100 - 1);
|
|
#else
|
|
return (x << 16) - (x << 8) - 1;
|
|
#endif
|
|
}
|
|
|
|
static long get_value(struct lru64_head *lru, long a)
|
|
{
|
|
struct lru64 *item;
|
|
|
|
if (lru) {
|
|
item = lru64_get(a, lru, lru, 0);
|
|
if (item && item->domain)
|
|
return (long)item->data;
|
|
}
|
|
misses++;
|
|
/* do the painful work here */
|
|
a = sum(a);
|
|
if (item)
|
|
lru64_commit(item, (void *)a, lru, 0);
|
|
return a;
|
|
}
|
|
|
|
/* pass #of loops in argv[1] and set argv[2] to something to use the LRU */
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct lru64_head *lru = NULL;
|
|
long long ret;
|
|
int total, loops;
|
|
|
|
if (argc < 2) {
|
|
printf("Need a number of rounds and optionally an LRU cache size (0..65536)\n");
|
|
exit(1);
|
|
}
|
|
|
|
total = atoi(argv[1]);
|
|
|
|
if (argc > 2) /* cache size */
|
|
lru = lru64_new(atoi(argv[2]));
|
|
|
|
ret = 0;
|
|
for (loops = 0; loops < total; loops++) {
|
|
ret += get_value(lru, rand() & 65535);
|
|
}
|
|
/* just for accuracy control */
|
|
printf("ret=%llx, hits=%d, misses=%d (%d %% hits)\n", ret, total-misses, misses, (int)((float)(total-misses) * 100.0 / total));
|
|
|
|
while (lru64_destroy(lru));
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|