mirror of
https://github.com/aaru-dps/libaaruformat.git
synced 2026-04-05 21:51:03 +00:00
lru: use native uint64 keys instead of string conversion
Replace string-keyed uthash lookups with HASH_FIND/HASH_ADD on native uint64_t keys. This eliminates per-lookup malloc/snprintf/strlen/free overhead from the block cache hot path (2 lookups per sector read). Before: malloc(17) + snprintf hex + HASH_FIND_STR + free per lookup After: HASH_FIND with 8-byte integer key, zero allocations Also removes the unused string-key API (find_in_cache/add_to_cache) and the uint64_to_string helper — all callers use uint64 keys. Fix off-by-one in eviction: >= caused cache to hold max_items-1 entries instead of max_items. Changed to > so the configured capacity is honored exactly.
This commit is contained in:
@@ -11,46 +11,40 @@
|
||||
/** \struct CacheEntry
|
||||
* \brief Single hash entry in the in-memory cache.
|
||||
*
|
||||
* This structure is managed by uthash (open addressing with chaining semantics provided by macros).
|
||||
* It represents one key/value association tracked by the cache. The cache implementation supports
|
||||
* both string keys (null-terminated) and 64-bit numeric keys; numeric keys are stored by casting
|
||||
* to a temporary string buffer upstream (see implementation). Callers do not allocate or free
|
||||
* individual entries directly; use the cache API helpers.
|
||||
* This structure is managed by uthash and represents one key/value association
|
||||
* tracked by the cache. Keys are native 64-bit integers, hashed directly by
|
||||
* uthash without string conversion. Callers do not allocate or free individual
|
||||
* entries directly; use the cache API helpers.
|
||||
*
|
||||
* Lifetime & ownership:
|
||||
* - key points either to a heap-allocated C string owned by the cache or to a short-lived buffer
|
||||
* duplicated internally; callers must not free it after insertion.
|
||||
* - value is an opaque pointer supplied by caller; the cache does not take ownership of the pointee
|
||||
* (caller remains responsible for the underlying object unless documented otherwise).
|
||||
* - value is an opaque pointer supplied by caller; the cache does not take
|
||||
* ownership unless a free_func is registered on the CacheHeader.
|
||||
*/
|
||||
struct CacheEntry
|
||||
{
|
||||
char *key; ///< Null-terminated key string (unique within the cache). May encode numeric keys.
|
||||
void *value; ///< Opaque value pointer associated with key (not freed automatically on eviction/clear).
|
||||
UT_hash_handle hh; ///< uthash handle linking this entry into the hash table (must remain last or per uthash docs).
|
||||
uint64_t key; ///< 64-bit integer key (unique within the cache).
|
||||
void *value; ///< Opaque value pointer associated with key.
|
||||
UT_hash_handle hh; ///< uthash handle (must remain per uthash docs).
|
||||
};
|
||||
|
||||
/** \struct CacheHeader
|
||||
* \brief Cache top-level descriptor encapsulating the hash table root and capacity limit.
|
||||
*
|
||||
* The cache enforces an upper bound (max_items) on the number of tracked entries. Insert helpers are expected
|
||||
* to evict (or refuse) when the limit is exceeded (strategy defined in implementation; current behavior may be
|
||||
* simple non-evicting if not yet implemented as a true LRU). The cache pointer holds the uthash root (NULL when
|
||||
* empty).
|
||||
* The cache enforces an upper bound (max_items) on the number of tracked entries. On insert,
|
||||
* the oldest entry is evicted when the limit is reached (LRU via uthash insertion order).
|
||||
*
|
||||
* Fields:
|
||||
* - max_items: Maximum number of entries allowed; 0 means "no explicit limit" if accepted by implementation.
|
||||
* - max_items: Maximum number of entries allowed; 0 means unlimited.
|
||||
* - cache: uthash root pointer; NULL when the cache is empty.
|
||||
* - free_func: Optional callback to free cached values on eviction/clear.
|
||||
*/
|
||||
struct CacheHeader
|
||||
{
|
||||
uint64_t max_items; ///< Hard limit for number of entries (policy: enforce/ignore depends on implementation).
|
||||
uint64_t max_items; ///< Hard limit for number of entries.
|
||||
struct CacheEntry *cache; ///< Hash root (uthash). NULL when empty.
|
||||
void (*free_func)(void *); ///< Optional callback to free cached values. NULL if values don't need freeing.
|
||||
void (*free_func)(void *); ///< Optional callback to free cached values. NULL if not needed.
|
||||
};
|
||||
|
||||
void *find_in_cache(struct CacheHeader *cache, const char *key);
|
||||
void add_to_cache(struct CacheHeader *cache, const char *key, void *value);
|
||||
void *find_in_cache_uint64(struct CacheHeader *cache, uint64_t key);
|
||||
void add_to_cache_uint64(struct CacheHeader *cache, uint64_t key, void *value);
|
||||
void free_cache(struct CacheHeader *cache);
|
||||
|
||||
109
src/lru.c
109
src/lru.c
@@ -1,69 +1,67 @@
|
||||
#include <inttypes.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <uthash.h>
|
||||
|
||||
#include <aaruformat.h>
|
||||
|
||||
// this is an example of how to do a LRU cache in C using uthash
|
||||
// http://uthash.sourceforge.net/
|
||||
// by Jehiah Czebotar 2011 - jehiah@gmail.com
|
||||
// this code is in the public domain http://unlicense.org/
|
||||
// LRU cache using uthash with native integer keys.
|
||||
// Based on uthash LRU example by Jehiah Czebotar 2011 (public domain).
|
||||
// Rewritten to use HASH_FIND/HASH_ADD with uint64_t keys directly,
|
||||
// eliminating all string conversion, malloc, and snprintf overhead
|
||||
// from the hot path.
|
||||
|
||||
/**
|
||||
* @brief Finds a value in the cache by string key.
|
||||
* @brief Finds a value in the cache by uint64_t key.
|
||||
*
|
||||
* Searches for a value in the cache using a string key and moves it to the front if found.
|
||||
* Searches for a value using a native 64-bit integer key and promotes it
|
||||
* to the front of the insertion-order list (LRU refresh) if found.
|
||||
*
|
||||
* @param cache Pointer to the cache header.
|
||||
* @param key String key to search for.
|
||||
* @param key 64-bit integer key to search for.
|
||||
* @return Pointer to the value if found, or NULL if not found.
|
||||
*/
|
||||
void *find_in_cache(struct CacheHeader *cache, const char *key)
|
||||
void *find_in_cache_uint64(struct CacheHeader *cache, const uint64_t key)
|
||||
{
|
||||
struct CacheEntry *entry;
|
||||
HASH_FIND_STR(cache->cache, key, entry);
|
||||
struct CacheEntry *entry = NULL;
|
||||
HASH_FIND(hh, cache->cache, &key, sizeof(uint64_t), entry);
|
||||
if(entry)
|
||||
{
|
||||
// remove it (so the subsequent add will throw it on the front of the list)
|
||||
// Remove and re-add to move to front of insertion-order list (LRU refresh).
|
||||
HASH_DELETE(hh, cache->cache, entry);
|
||||
HASH_ADD_KEYPTR(hh, cache->cache, entry->key, strlen(entry->key), entry);
|
||||
HASH_ADD(hh, cache->cache, key, sizeof(uint64_t), entry);
|
||||
return entry->value;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a value to the cache with a string key, pruning if necessary.
|
||||
* @brief Adds a value to the cache with a uint64_t key, evicting LRU if full.
|
||||
*
|
||||
* Adds a new entry to the cache. If the cache exceeds its maximum size, prunes the least recently used entry.
|
||||
* Adds a new entry to the cache. If the cache exceeds its maximum size,
|
||||
* evicts the least recently used (oldest insertion-order) entry.
|
||||
*
|
||||
* @param cache Pointer to the cache header.
|
||||
* @param key String key to add.
|
||||
* @param key 64-bit integer key to add.
|
||||
* @param value Pointer to the value to store.
|
||||
*/
|
||||
void add_to_cache(struct CacheHeader *cache, const char *key, void *value)
|
||||
void add_to_cache_uint64(struct CacheHeader *cache, const uint64_t key, void *value)
|
||||
{
|
||||
struct CacheEntry *entry;
|
||||
// TODO: Is this needed or we're just losing cycles? uthash does not free the entry
|
||||
entry = malloc(sizeof(struct CacheEntry));
|
||||
entry->key = strdup(key);
|
||||
entry->value = value;
|
||||
HASH_ADD_KEYPTR(hh, cache->cache, entry->key, strlen(entry->key), entry);
|
||||
struct CacheEntry *entry = malloc(sizeof(struct CacheEntry));
|
||||
if(!entry) return;
|
||||
|
||||
// prune the cache to MAX_CACHE_SIZE
|
||||
if(HASH_COUNT(cache->cache) >= cache->max_items)
|
||||
entry->key = key;
|
||||
entry->value = value;
|
||||
HASH_ADD(hh, cache->cache, key, sizeof(uint64_t), entry);
|
||||
|
||||
// Evict oldest entry if cache exceeded capacity.
|
||||
if(HASH_COUNT(cache->cache) > cache->max_items)
|
||||
{
|
||||
struct CacheEntry *tmp_entry;
|
||||
HASH_ITER(hh, cache->cache, entry, tmp_entry)
|
||||
{
|
||||
// prune the first entry (loop is based on insertion order so this deletes the oldest item)
|
||||
HASH_DELETE(hh, cache->cache, entry);
|
||||
free(entry->key);
|
||||
|
||||
// Free the cached value if a free function is registered
|
||||
if(cache->free_func && entry->value)
|
||||
cache->free_func(entry->value);
|
||||
|
||||
@@ -73,57 +71,11 @@ void add_to_cache(struct CacheHeader *cache, const char *key, void *value)
|
||||
}
|
||||
}
|
||||
|
||||
FORCE_INLINE char *uint64_to_string(const uint64_t number)
|
||||
{
|
||||
char *char_key = malloc(17); // 16 hex digits + null terminator
|
||||
if(!char_key) return NULL;
|
||||
snprintf(char_key, 17, "%016" PRIX64, number);
|
||||
return char_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Finds a value in the cache by uint64_t key, using string conversion.
|
||||
*
|
||||
* Converts the uint64_t key to a string and searches for the entry in the cache.
|
||||
*
|
||||
* @param cache Pointer to the cache header.
|
||||
* @param key 64-bit integer key to search for.
|
||||
* @return Pointer to the value if found, or NULL if not found.
|
||||
*/
|
||||
void *find_in_cache_uint64(struct CacheHeader *cache, const uint64_t key)
|
||||
{
|
||||
char *char_key = uint64_to_string(key);
|
||||
if(!char_key) return NULL;
|
||||
|
||||
void *result = find_in_cache(cache, char_key);
|
||||
free(char_key); // Free the temporary string to prevent memory leak
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Adds a value to the cache with a uint64_t key, using string conversion.
|
||||
*
|
||||
* Converts the uint64_t key to a string and adds the entry to the cache.
|
||||
*
|
||||
* @param cache Pointer to the cache header.
|
||||
* @param key 64-bit integer key to add.
|
||||
* @param value Pointer to the value to store.
|
||||
*/
|
||||
void add_to_cache_uint64(struct CacheHeader *cache, const uint64_t key, void *value)
|
||||
{
|
||||
char *char_key = uint64_to_string(key);
|
||||
if(!char_key) return;
|
||||
|
||||
add_to_cache(cache, char_key, value);
|
||||
free(char_key); // Free the temporary string (add_to_cache makes its own copy with strdup)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Frees all entries in the cache and clears it.
|
||||
*
|
||||
* Iterates through all cache entries, frees their keys and the entries themselves,
|
||||
* then clears the cache hash table. Uses the cache's free_func if set to free cached values.
|
||||
* Iterates through all cache entries, frees them, then clears the hash table.
|
||||
* Uses the cache's free_func if set to free cached values.
|
||||
*
|
||||
* @param cache Pointer to the cache header.
|
||||
*/
|
||||
@@ -136,9 +88,7 @@ void free_cache(struct CacheHeader *cache)
|
||||
HASH_ITER(hh, cache->cache, entry, tmp)
|
||||
{
|
||||
HASH_DELETE(hh, cache->cache, entry);
|
||||
free(entry->key);
|
||||
|
||||
// Free the cached value if a free function is registered
|
||||
if(cache->free_func && entry->value)
|
||||
cache->free_func(entry->value);
|
||||
|
||||
@@ -147,4 +97,3 @@ void free_cache(struct CacheHeader *cache)
|
||||
|
||||
cache->cache = NULL;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user