1
0
Fork 0
knot-resolver/lib/generic/lru.h
Daniel Baumann fbc604e215
Adding upstream version 5.7.5.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-21 13:56:17 +02:00

243 lines
7.8 KiB
C

/* Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/**
* @file lru.h
* @brief A lossy cache.
*
* @note The implementation tries to keep frequent keys and avoid others,
* even if "used recently", so it may refuse to store it on lru_get_new().
* It uses hashing to split the problem pseudo-randomly into smaller groups,
* and within each it tries to approximate relative usage counts of several
* most frequent keys/hashes. This tracking is done for *more* keys than
* those that are actually stored.
*
* Example usage:
* @code{.c}
* // Define new LRU type
* typedef lru_t(int) lru_int_t;
*
* // Create LRU
* lru_int_t *lru;
* lru_create(&lru, 5, NULL, NULL);
*
* // Insert some values
* int *pi = lru_get_new(lru, "luke", strlen("luke"), NULL);
* if (pi)
* *pi = 42;
* pi = lru_get_new(lru, "leia", strlen("leia"), NULL);
* if (pi)
* *pi = 24;
*
* // Retrieve values
* int *ret = lru_get_try(lru, "luke", strlen("luke"), NULL);
* if (!ret) printf("luke dropped out!\n");
* else printf("luke's number is %d\n", *ret);
*
* char *enemies[] = {"goro", "raiden", "subzero", "scorpion"};
* for (int i = 0; i < 4; ++i) {
* int *val = lru_get_new(lru, enemies[i], strlen(enemies[i]), NULL);
* if (val)
* *val = i;
* }
*
* // We're done
* lru_free(lru);
* @endcode
*
* \addtogroup generics
* @{
*/
#pragma once
#include <stdalign.h>
#include <stddef.h>
#include <stdint.h>
#include "contrib/ucw/lib.h"
#include "lib/utils.h"
#include "libknot/mm_ctx.h"
/* ================================ Interface ================================ */
/** @brief The type for LRU, parametrized by value type. */
#define lru_t(type) \
union { \
type *pdata_t; /* only the *type* information is used */ \
struct lru lru; \
}
/**
* @brief Allocate and initialize an LRU with default associativity.
*
* The real limit on the number of slots can be a bit larger but less than double.
*
* @param ptable pointer to a pointer to the LRU
* @param max_slots number of slots
* @param mm_ctx_array memory context to use for the huge array, NULL for default
* If you pass your own, it needs to produce CACHE_ALIGNED allocations (ubsan).
* @param mm_ctx memory context to use for individual key-value pairs, NULL for default
*
* @note The pointers to memory contexts need to remain valid
* during the whole life of the structure (or be NULL).
*/
/* Pragmas: C11 only standardizes alignof on type names, not on expressions.
* That's a GNU extension; in clang it's supported but may generate warnings.
* It seems hard to disable warnings that are only supported by some compilers. */
#define lru_create(ptable, max_slots, mm_ctx_array, mm_ctx) do { \
(void)(((__typeof__((*(ptable))->pdata_t))0) == (void *)0); /* typecheck lru_t */ \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wpragmas\"") \
_Pragma("GCC diagnostic ignored \"-Wunknown-pragmas\"") \
_Pragma("GCC diagnostic ignored \"-Wgnu-alignof-expression\"") \
*(ptable) = (__typeof__(*(ptable))) \
lru_create_impl((max_slots), alignof(*( (*(ptable))->pdata_t )), \
(mm_ctx_array), (mm_ctx)); \
_Pragma("GCC diagnostic pop") \
} while (false)
/** @brief Free an LRU created by lru_create (it can be NULL). */
#define lru_free(table) \
lru_free_impl(&(table)->lru)
/** @brief Reset an LRU to the empty state (but preserve any settings). */
#define lru_reset(table) \
lru_reset_impl(&(table)->lru)
/**
* @brief Find key in the LRU and return pointer to the corresponding value.
*
* @param table pointer to LRU
* @param key_ lookup key
* @param len_ key length
* @return pointer to data or NULL if not found
*/
#define lru_get_try(table, key_, len_) \
(__typeof__((table)->pdata_t)) \
lru_get_impl(&(table)->lru, (key_), (len_), -1, false, NULL)
/**
* @brief Return pointer to value, inserting if needed (zeroed).
*
* @param table pointer to LRU
* @param key_ lookup key
* @param len_ key lengthkeys
* @param is_new pointer to bool to store result of operation
* (true if entry is newly added, false otherwise; can be NULL).
* @return pointer to data or NULL (can be even if memory could be allocated!)
*/
#define lru_get_new(table, key_, len_, is_new) \
(__typeof__((table)->pdata_t)) \
lru_get_impl(&(table)->lru, (key_), (len_), \
lru_member_size((table)), true, is_new)
#define lru_member_size(table) \
(sizeof(*(table)->pdata_t)) // NOLINT(bugprone-sizeof-expression): usually a false-positive
/**
* @brief Apply a function to every item in LRU.
*
* @param table pointer to LRU
* @param function enum lru_apply_do (*function)(const char *key, uint len, val_type *val, void *baton)
* See enum lru_apply_do for the return type meanings.
* @param baton extra pointer passed to each function invocation
*/
#define lru_apply(table, function, baton) do { \
lru_apply_fun_g(fun_dummy, __typeof__(*(table)->pdata_t)) = 0; \
(void)(fun_dummy == (function)); /* produce a warning with incompatible function type */ \
lru_apply_impl(&(table)->lru, (lru_apply_fun)(function), (baton)); \
} while (false)
/** @brief Possible actions to do with an element. */
enum lru_apply_do {
LRU_APPLY_DO_NOTHING,
LRU_APPLY_DO_EVICT,
/* maybe more in future*/
};
/**
* @brief Return the real capacity - maximum number of keys holdable within.
*
* @param table pointer to LRU
*/
#define lru_capacity(table) lru_capacity_impl(&(table)->lru)
/* ======================== Inlined part of implementation ======================== */
/** @cond internal */
#define lru_apply_fun_g(name, val_type) \
enum lru_apply_do (*(name))(const char *key, uint len, val_type *val, void *baton)
typedef lru_apply_fun_g(lru_apply_fun, void);
#if __GNUC__ >= 4
#define CACHE_ALIGNED __attribute__((aligned(64)))
#else
#define CACHE_ALIGNED
#endif
struct lru;
void lru_free_items_impl(struct lru *lru);
struct lru * lru_create_impl(uint max_slots, uint val_alignment,
knot_mm_t *mm_array, knot_mm_t *mm);
void * lru_get_impl(struct lru *lru, const char *key, uint key_len,
uint val_len, bool do_insert, bool *is_new);
void lru_apply_impl(struct lru *lru, lru_apply_fun f, void *baton);
struct lru_item;
#if SIZE_MAX > (1 << 32)
/** @internal The number of keys stored within each group. */
#define LRU_ASSOC 3
#else
#define LRU_ASSOC 4
#endif
/** @internal The number of hashes tracked within each group: 10-1 or 12-1. */
#define LRU_TRACKED ((64 - sizeof(size_t) * LRU_ASSOC) / 4 - 1)
struct lru_group {
uint16_t counts[LRU_TRACKED+1]; /*!< Occurrence counters; the last one is special. */
uint16_t hashes[LRU_TRACKED+1]; /*!< Top halves of hashes; the last one is unused. */
struct lru_item *items[LRU_ASSOC]; /*!< The full items. */
} CACHE_ALIGNED;
/* The sizes are chosen so lru_group just fits into a single x86 cache line. */
static_assert(64 == sizeof(struct lru_group)
&& 64 == LRU_ASSOC * sizeof(void*) + (LRU_TRACKED+1) * 4,
"bad sizing for your sizeof(void*)");
struct lru {
struct knot_mm *mm, /**< Memory context to use for keys. */
*mm_array; /**< Memory context to use for this structure itself. */
uint log_groups; /**< Logarithm of the number of LRU groups. */
uint val_alignment; /**< Alignment for the values. */
struct lru_group groups[] CACHE_ALIGNED; /**< The groups of items. */
};
/** @internal See lru_free. */
static inline void lru_free_impl(struct lru *lru)
{
if (!lru)
return;
lru_free_items_impl(lru);
mm_free(lru->mm_array, lru);
}
/** @internal See lru_reset. */
static inline void lru_reset_impl(struct lru *lru)
{
lru_free_items_impl(lru);
memset(lru->groups, 0, sizeof(lru->groups[0]) * (1 << lru->log_groups));
}
/** @internal See lru_capacity. */
static inline uint lru_capacity_impl(struct lru *lru)
{
kr_require(lru);
return (1 << lru->log_groups) * LRU_ASSOC;
}
/** @endcond */
/** @} (addtogroup generics) */