diff --git a/include/aaruformat/lru.h b/include/aaruformat/lru.h index e1af76b..24b7b68 100644 --- a/include/aaruformat/lru.h +++ b/include/aaruformat/lru.h @@ -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); diff --git a/src/lru.c b/src/lru.c index 47c2aa1..bc5d99b 100644 --- a/src/lru.c +++ b/src/lru.c @@ -1,69 +1,67 @@ -#include #include #include #include -#include #include #include -// 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; } -