diff --git a/lib/generic/README.rst b/lib/generic/README.rst index 3bb1e18cf12680995110d864fad406cd49d9b282..0fb042f45bbf3c6ecb6ea731c9b17d5e908a9245 100644 --- a/lib/generic/README.rst +++ b/lib/generic/README.rst @@ -10,6 +10,7 @@ as long as it comes with a test case in `tests/test_generics.c`. * map_ - a `Crit-bit tree`_ key-value map implementation (public domain) that comes with tests. * set_ - set abstraction implemented on top of ``map``. * pack_ - length-prefixed list of objects (i.e. array-list). +* lru_ - LRU-like hash table array ~~~~~ @@ -35,4 +36,10 @@ pack .. doxygenfile:: pack.h :project: libkresolve +lru +~~~ + +.. doxygenfile:: lru.h + :project: libkresolve + .. _`Crit-bit tree`: http://cr.yp.to/critbit.html diff --git a/lib/generic/lru.h b/lib/generic/lru.h new file mode 100644 index 0000000000000000000000000000000000000000..9060a183b3e724ba2f1ef63d25b5017e19ca5994 --- /dev/null +++ b/lib/generic/lru.h @@ -0,0 +1,199 @@ +/* Copyright (C) 2013 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +/** + * @file lru.h + * @brief LRU-like cache. + * + * @note This is a naive LRU implementation, if value exists it is treated as old + * if the key collides. This may be improved with double hashing or hopscotch. + * + * # Example usage: + * + * @code{.c} + * // Define new LRU type + * typedef lru_hash(int) lru_int_t; + * + * // Create LRU on stack + * size_t lru_size = lru_size(lru_int_t, 10); + * lru_int_t lru[lru_size]; + * lru_init(&lru, 5); + * + * // Insert some values + * *lru_set(&lru, "luke", strlen("luke")) = 42; + * *lru_set(&lru, "leia", strlen("leia")) = 24; + * + * // Retrieve values + * int *ret = lru_get(&lru, "luke", strlen("luke"); + * if (ret) printf("luke dropped out!\n"); + * else printf("luke's number is %d\n", *ret); + * + * // Set up eviction function, this is going to get called + * // on entry eviction (baton refers to baton in 'lru' structure) + * void on_evict(void *baton, void *data_) { + * int *data = (int *) data; + * printf("number %d dropped out!\n", *data); + * } + * char *enemies[] = {"goro", "raiden", "subzero", "scorpion"}; + * for (int i = 0; i < 4; ++i) { + * *lru_set(&lru, enemies[i], strlen(enemies[i])) = i; + * } + * + * // We're done + * lru_deinit(&lru); + * @endcode + * + * \addtogroup generics + * @{ + */ + +#pragma once + +#include <stdlib.h> +#include <stdint.h> +#include "contrib/murmurhash3/murmurhash3.h" + +#define lru_slot_struct \ + char *key; /**< Slot key */ \ + uint32_t len; /**< Slot length */ \ +/** @brief Slot header. */ +struct lru_slot { + lru_slot_struct +}; + +/** @brief Return boolean true if slot matches key/len pair. */ +static inline int lru_slot_match(struct lru_slot *slot, const char *key, uint32_t len) +{ + return (slot->len == len) && (memcmp(slot->key, key, len) == 0); +} + +#define lru_slot_offset(table) \ + (size_t)((void *)&((table)->slots[0].data) - (void *)&((table)->slots[0])) + +/** @brief Callback definitions. */ +typedef void (*lru_free_f)(void *baton, void *ptr); + +/** @brief LRU structure base. */ +#define lru_hash_struct \ + uint32_t size; /**< Number of slots */ \ + uint32_t stride; /**< Stride of the 'slots' array */ \ + lru_free_f evict; /**< Eviction function */ \ + void *baton; /**< Passed to eviction function */ +/** @internal Object base of any other lru_hash type. */ +struct lru_hash_base { + lru_hash_struct + char slots[]; +}; + +/** @breif User-defined hashtable. */ +#define lru_hash(type) \ +struct { \ + lru_hash_struct \ + struct { \ + lru_slot_struct \ + type data; \ + } slots[]; \ +} + +/** @internal Slot data getter */ +static inline void *lru_slot_get(struct lru_hash_base *lru, const char *key, uint32_t len, size_t offset) +{ + uint32_t id = hash(key, len) % lru->size; + struct lru_slot *slot = (struct lru_slot *)(lru->slots + (id * lru->stride)); + if (lru_slot_match(slot, key, len)) { + return ((char *)slot) + offset; + } + return NULL; +} + +/** @internal Slot data setter */ +static inline void *lru_slot_set(struct lru_hash_base *lru, const char *key, uint32_t len, size_t offset) +{ + uint32_t id = hash(key, len) % lru->size; + struct lru_slot *slot = (struct lru_slot *)(lru->slots + (id * lru->stride)); + if (!lru_slot_match(slot, key, len)) { + if (slot->key) { + free(slot->key); + if (lru->evict) { + lru->evict(lru->baton, ((char *)slot) + offset); + } + } + memset(slot, 0, lru->stride); + slot->key = malloc(len); + if (!slot->key) { + slot->len = 0; + return NULL; + } + memcpy(slot->key, key, len); + slot->len = len; + } + return ((char *)slot) + offset; +} + +/** + * @brief Return size of the LRU structure with given number of slots. + * @param type type of LRU structure + * @param max_slots number of slots + */ +#define lru_size(type, max_slots) \ + (sizeof(type) + (max_slots) * sizeof(((type *)NULL)->slots[0])) + +/** + * @brief Initialize hash table. + * @param table hash table + * @param max_slots number of slots + */ +#define lru_init(table, max_size) \ + (memset((table), 0, sizeof(*table) + (max_size) * sizeof((table)->slots[0])), \ + (table)->stride = sizeof((table)->slots[0]), (table)->size = (max_size)) + +/** + * @brief Free all keys and evict all values. + * @param table hash table + */ +#define lru_deinit(table) if (table) { \ + for (uint32_t i = 0; i < (table)->size; ++i) { \ + if ((table)->slots[i].key) { \ + if ((table)->evict) { \ + (table)->evict((table)->baton, &(table)->slots[i].data); \ + } \ + free((table)->slots[i].key); \ + } \ + } \ +} + +/** + * @brief Find key in the hash table and return pointer to it's value. + * @param table hash table + * @param key_ lookup key + * @param len_ key length + * @return pointer to data or NULL + */ +#define lru_get(table, key_, len_) \ + (__typeof__(&(table)->slots[0].data)) \ + lru_slot_get((struct lru_hash_base *)(table), (key_), (len_), lru_slot_offset(table)) + +/** + * @brief Return pointer to value (create/replace if needed) + * @param table hash table + * @param key_ lookup key + * @param len_ key length + * @return pointer to data or NULL + */ +#define lru_set(table, key_, len_) \ + (__typeof__(&(table)->slots[0].data)) \ + lru_slot_set((struct lru_hash_base *)(table), (key_), (len_), lru_slot_offset(table)) + +/** @} */ diff --git a/tests/unit.mk b/tests/unit.mk index d2f2f59ead6c6d02ca74874e0f2651410b5b5444..24dc1d00226195c2cbcdd1b1d13c3a5930f5ce89 100644 --- a/tests/unit.mk +++ b/tests/unit.mk @@ -7,6 +7,7 @@ tests_BIN := \ test_map \ test_array \ test_pack \ + test_lru \ test_utils \ test_module \ test_cache \