libaaruformat 1.0
Aaru Data Preservation Suite - Format Library
Loading...
Searching...
No Matches
static_lru_hash_map.c
Go to the documentation of this file.
1/*
2 * This file is part of the Aaru Data Preservation Suite.
3 * Copyright (c) 2019-2026 Natalia Portillo.
4 *
5 * This library is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as
7 * published by the Free Software Foundation; either version 2.1 of the
8 * License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
17 */
18
37
38#include <stdbool.h>
39#include <stdint.h>
40#include <stdlib.h>
41#include <string.h>
42
44
45/* ============================================================================
46 * Internal Constants
47 * ============================================================================ */
48
49#define MIN_ACCESS_COUNT 1
50#define TOMBSTONE_KEY 0
51
52/* ============================================================================
53 * Internal Helper Functions
54 * ============================================================================ */
55
65{
66 for(size_t i = 0; i < map->size; i++)
67 {
68 if(map->table[i].key != TOMBSTONE_KEY && map->table[i].access_count > 1)
69 {
70 // Halve with rounding up to avoid zeroing out entries too quickly
71 map->table[i].access_count = (map->table[i].access_count + 1) / 2;
72 }
73 }
74}
75
88{
89 lru_kv_pair_t *old_table = map->table;
90 size_t old_count = map->count;
91
92 // Allocate new table (temporary allocation during rehash)
93 map->table = calloc(map->size, sizeof(lru_kv_pair_t));
94 if(!map->table)
95 {
96 // Allocation failed, restore old table
97 map->table = old_table;
98 return;
99 }
100
101 map->count = 0;
102
103 // Re-insert all non-empty entries
104 for(size_t i = 0; i < map->size; i++)
105 {
106 if(old_table[i].key != TOMBSTONE_KEY)
107 {
108 size_t idx = old_table[i].key % map->size;
109
110 while(map->table[idx].key != TOMBSTONE_KEY) idx = (idx + 1) % map->size;
111
112 map->table[idx] = old_table[i];
113 map->count++;
114 }
115 }
116
117 free(old_table);
118
119 // Sanity check - count should match what we had before eviction
120 (void)old_count; // Avoid unused variable warning in release builds
121}
122
139static size_t evict_lru_entries(static_lru_hash_map_t *map, size_t target_count_out)
140{
141 if(target_count_out == 0) target_count_out = map->target_count;
142
143 // Nothing to evict if we're already below target
144 if(map->count <= target_count_out) return 0;
145
146 size_t entries_to_remove = map->count - target_count_out;
147
148 // Step 1: Build histogram of access counts
149 // histogram[i] = number of entries with access_count == i
150 size_t histogram[256] = {0};
151
152 for(size_t i = 0; i < map->size; i++)
153 {
154 if(map->table[i].key != TOMBSTONE_KEY) histogram[map->table[i].access_count]++;
155 }
156
157 // Step 2: Find cutoff access_count
158 // We want to evict entries with the lowest access counts
159 // Find the threshold such that entries with count <= threshold covers entries_to_remove
160 uint8_t cutoff = 0;
161 size_t cumulative = 0;
162
163 for(int i = 0; i < 256; i++)
164 {
165 cumulative += histogram[i];
166 if(cumulative >= entries_to_remove)
167 {
168 cutoff = (uint8_t)i;
169 break;
170 }
171 }
172
173 // Step 3: Evict entries with access_count <= cutoff
174 size_t removed = 0;
175
176 for(size_t i = 0; i < map->size && removed < entries_to_remove; i++)
177 {
178 if(map->table[i].key != TOMBSTONE_KEY && map->table[i].access_count <= cutoff)
179 {
180 map->table[i].key = TOMBSTONE_KEY;
181 map->table[i].value = 0;
182 map->table[i].access_count = 0;
183 removed++;
184 }
185 }
186
187 map->count -= removed;
188
189#ifdef STATIC_LRU_ENABLE_STATS
190 map->eviction_count += removed;
191 map->eviction_cycles++;
192#endif
193
194 // Step 4: Rehash to fix probe chains
195 rehash_in_place(map);
196
197 return removed;
198}
199
200/* ============================================================================
201 * Public API Implementation
202 * ============================================================================ */
203
205{
206 // Enforce minimum size
208
209 static_lru_hash_map_t *map = malloc(sizeof(static_lru_hash_map_t));
210 if(!map) return NULL;
211
212 map->table = calloc(size, sizeof(lru_kv_pair_t));
213 if(!map->table)
214 {
215 free(map);
216 return NULL;
217 }
218
219 map->size = size;
220 map->count = 0;
221 map->max_count = (size_t)(size * STATIC_LRU_EVICTION_LOAD_FACTOR);
222 map->target_count = (size_t)(size * STATIC_LRU_TARGET_LOAD_FACTOR);
223 map->age_counter = 0;
224 map->_padding = 0;
225
226#ifdef STATIC_LRU_ENABLE_STATS
227 map->total_lookups = 0;
228 map->cache_hits = 0;
229 map->total_inserts = 0;
230 map->eviction_count = 0;
231 map->eviction_cycles = 0;
232#endif
233
234 return map;
235}
236
238{
239 if(!map) return;
240
241 free(map->table);
242 free(map);
243}
244
245bool static_lru_insert_map(static_lru_hash_map_t *map, uint64_t key, uint64_t value)
246{
247 // Trigger eviction if we've exceeded the threshold
248 if(map->count >= map->max_count) evict_lru_entries(map, 0);
249
250 // Periodic aging of access counts
251 map->age_counter++;
253 {
255 map->age_counter = 0;
256 }
257
258#ifdef STATIC_LRU_ENABLE_STATS
259 map->total_inserts++;
260#endif
261
262 // Linear probing to find slot
263 size_t idx = key % map->size;
264
265 while(map->table[idx].key != TOMBSTONE_KEY && map->table[idx].key != key) idx = (idx + 1) % map->size;
266
267 if(map->table[idx].key == key)
268 {
269 // Key exists - update value and boost access count
270 map->table[idx].value = value;
271 if(map->table[idx].access_count < 255) map->table[idx].access_count++;
272 return false; // Not a new key
273 }
274
275 // New key insertion
276 map->table[idx].key = key;
277 map->table[idx].value = value;
279 map->count++;
280
281 return true;
282}
283
284bool static_lru_lookup_map(static_lru_hash_map_t *map, uint64_t key, uint64_t *out_value)
285{
286#ifdef STATIC_LRU_ENABLE_STATS
287 map->total_lookups++;
288#endif
289
290 size_t idx = key % map->size;
291
292 while(map->table[idx].key != TOMBSTONE_KEY)
293 {
294 if(map->table[idx].key == key)
295 {
296 *out_value = map->table[idx].value;
297
298 // Increment access count (saturate at 255)
299 if(map->table[idx].access_count < 255) map->table[idx].access_count++;
300
301#ifdef STATIC_LRU_ENABLE_STATS
302 map->cache_hits++;
303#endif
304
305 return true;
306 }
307
308 idx = (idx + 1) % map->size;
309 }
310
311 return false;
312}
313
314bool static_lru_contains_key(const static_lru_hash_map_t *map, uint64_t key)
315{
316 size_t idx = key % map->size;
317
318 while(map->table[idx].key != TOMBSTONE_KEY)
319 {
320 if(map->table[idx].key == key) return true;
321
322 idx = (idx + 1) % map->size;
323 }
324
325 return false;
326}
327
328size_t static_lru_evict(static_lru_hash_map_t *map, size_t entries_to_keep)
329{ return evict_lru_entries(map, entries_to_keep); }
330
336
337double static_lru_load_factor(const static_lru_hash_map_t *map) { return (double)map->count / (double)map->size; }
338
339size_t static_lru_free_slots(const static_lru_hash_map_t *map) { return map->size - map->count; }
340
341#ifdef STATIC_LRU_ENABLE_STATS
342double static_lru_hit_rate(const static_lru_hash_map_t *map)
343{
344 if(map->total_lookups == 0) return 0.0;
345
346 return (double)map->cache_hits / (double)map->total_lookups;
347}
348
349void static_lru_reset_stats(static_lru_hash_map_t *map)
350{
351 map->total_lookups = 0;
352 map->cache_hits = 0;
353 map->total_inserts = 0;
354 map->eviction_count = 0;
355 map->eviction_cycles = 0;
356}
357#endif
static void rehash_in_place(static_lru_hash_map_t *map)
Rehashes all entries in place after eviction.
void static_lru_free_map(static_lru_hash_map_t *map)
Frees all memory associated with a static LRU hash map.
static void age_access_counts(static_lru_hash_map_t *map)
Ages all access counts by halving them.
void static_lru_age_counts(static_lru_hash_map_t *map)
Manually ages all access counts.
size_t static_lru_free_slots(const static_lru_hash_map_t *map)
Returns the number of free slots available.
static_lru_hash_map_t * static_lru_create_map(size_t size)
Creates a new static LRU hash map with fixed size.
size_t static_lru_evict(static_lru_hash_map_t *map, size_t entries_to_keep)
Manually triggers eviction of least-used entries.
bool static_lru_lookup_map(static_lru_hash_map_t *map, uint64_t key, uint64_t *out_value)
Looks up a value by key in the static LRU hash map.
double static_lru_load_factor(const static_lru_hash_map_t *map)
Returns the current load factor of the map.
#define TOMBSTONE_KEY
Marker for empty/deleted slots (key=0 reserved).
static size_t evict_lru_entries(static_lru_hash_map_t *map, size_t target_count_out)
Evicts least-frequently-used entries to make room for new insertions.
bool static_lru_insert_map(static_lru_hash_map_t *map, uint64_t key, uint64_t value)
Inserts a key-value pair into the static LRU hash map.
#define MIN_ACCESS_COUNT
New entries start with this access count.
bool static_lru_contains_key(const static_lru_hash_map_t *map, uint64_t key)
Checks if a key exists in the map WITHOUT updating access count.
Static-memory hash map with LRU-like eviction for fixed RAM usage.
#define STATIC_LRU_TARGET_LOAD_FACTOR
Evict down to 75% capacity.
#define STATIC_LRU_MIN_SIZE
Minimum map size to prevent edge cases.
#define STATIC_LRU_EVICTION_LOAD_FACTOR
Default configuration constants for the static LRU hash map.
#define STATIC_LRU_AGING_INTERVAL
Age access counts every N operations.
Single key/value slot with access tracking for the static LRU hash map.
uint8_t access_count
Access frequency counter (0-255, saturates at 255).
uint64_t value
Associated value payload (64-bit).
uint64_t key
Stored key (64-bit). 0 indicates an empty slot.
Fixed-size hash map with LRU-like eviction for bounded memory usage.
size_t max_count
Eviction trigger threshold (size * EVICTION_LOAD_FACTOR).
uint32_t age_counter
Operations since last aging.
size_t target_count
Target count after eviction (size * TARGET_LOAD_FACTOR).
size_t count
Number of active (filled) entries.
lru_kv_pair_t * table
Array of key/value slots of length == size.
size_t size
Allocated slot capacity (FIXED at creation).
uint32_t _padding
Padding for alignment.