diff --git a/libCacheSim/bin/cachesim/cache_init.h b/libCacheSim/bin/cachesim/cache_init.h index 52c7363b..44896e84 100644 --- a/libCacheSim/bin/cachesim/cache_init.h +++ b/libCacheSim/bin/cachesim/cache_init.h @@ -56,6 +56,7 @@ static inline cache_t *create_cache(const char *trace_path, {"lfuda", LFUDA_init}, {"lirs", LIRS_init}, {"lru", LRU_init}, + {"lru-k", LRU_K_init}, {"lru-prob", LRU_Prob_init}, {"nop", nop_init}, // plugin cache that allows user to implement custom cache diff --git a/libCacheSim/cache/CMakeLists.txt b/libCacheSim/cache/CMakeLists.txt index 7f44aded..bbc797e7 100644 --- a/libCacheSim/cache/CMakeLists.txt +++ b/libCacheSim/cache/CMakeLists.txt @@ -75,6 +75,7 @@ set(eviction_sources_c set(eviction_sources_cpp eviction/cpp/LFU.cpp eviction/cpp/GDSF.cpp + eviction/cpp/LRU_K.cpp eviction/LHD/lhd.cpp eviction/LHD/LHD_Interface.cpp ) diff --git a/libCacheSim/cache/eviction/cpp/LRU_K.cpp b/libCacheSim/cache/eviction/cpp/LRU_K.cpp index e69de29b..ab2148e5 100644 --- a/libCacheSim/cache/eviction/cpp/LRU_K.cpp +++ b/libCacheSim/cache/eviction/cpp/LRU_K.cpp @@ -0,0 +1,350 @@ +/* LRU-K: Evict the object with the largest backward K-distance. + * + * Objects that have been accessed fewer than K times have an infinite + * backward K-distance and are evicted first in FIFO order. + * Among objects with >= K accesses, the one with the oldest K-th most + * recent access time is evicted first. + * + * Reference: O'Neil, O'Neil, Weikum. "The LRU-K Page Replacement Algorithm + * for Database Disk Buffering." ACM SIGMOD 1993. + * + * Parameters: + * k: the number of recent accesses to track (default: 2) + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "abstractRank.hpp" + +namespace eviction { + +class LRU_K { + public: + int k; + + /* Objects with < K accesses are held in a FIFO queue. + * We use a doubly-linked list + map for O(1) removal. */ + std::list fifo_queue; + std::unordered_map::iterator> + fifo_map; + + /* Objects with >= K accesses are held in a priority queue sorted by + * their K-th most recent access time (ascending = evict first). */ + using pq_entry_t = std::pair; + std::set pq; + std::unordered_map pq_map; // obj -> K-th vtime + + /* Per-object access history: the K most recent request vtimes. + * front() = oldest (K-th most recent), back() = most recent. */ + std::unordered_map> history; + + explicit LRU_K(int k_param = 2) : k(k_param) {} +}; + +} // namespace eviction + +#ifdef __cplusplus +extern "C" { +#endif + +// *********************************************************************** +// **** **** +// **** function declarations **** +// **** **** +// *********************************************************************** + +cache_t *LRU_K_init(const common_cache_params_t ccache_params, + const char *cache_specific_params); +static void LRU_K_free(cache_t *cache); +static bool LRU_K_get(cache_t *cache, const request_t *req); + +static cache_obj_t *LRU_K_find(cache_t *cache, const request_t *req, + bool update_cache); +static cache_obj_t *LRU_K_insert(cache_t *cache, const request_t *req); +static cache_obj_t *LRU_K_to_evict(cache_t *cache, const request_t *req); +static void LRU_K_evict(cache_t *cache, const request_t *req); +static bool LRU_K_remove(cache_t *cache, obj_id_t obj_id); + +// *********************************************************************** +// **** **** +// **** end user facing functions **** +// **** **** +// **** init, free, get **** +// *********************************************************************** + +/** + * @brief parse algorithm-specific parameters + * + * Supported parameters: + * k= number of accesses to track (default: 2) + */ +static void LRU_K_parse_params(cache_t *cache, + const char *cache_specific_params) { + auto *lruk = + reinterpret_cast(cache->eviction_params); + if (cache_specific_params == nullptr || cache_specific_params[0] == '\0') + return; + + const char *k_str = strstr(cache_specific_params, "k="); + if (k_str != nullptr) { + int k_val = atoi(k_str + 2); + if (k_val >= 1) { + lruk->k = k_val; + } else { + WARN("LRU_K: invalid k value %d, using default k=2\n", k_val); + } + } +} + +/** + * @brief initialize the cache + * + * @param ccache_params some common cache parameters + * @param cache_specific_params cache specific parameters, e.g. "k=2" + */ +cache_t *LRU_K_init(const common_cache_params_t ccache_params, + const char *cache_specific_params) { + cache_t *cache = + cache_struct_init("LRU_K", ccache_params, cache_specific_params); + cache->eviction_params = reinterpret_cast(new eviction::LRU_K(2)); + + cache->cache_init = LRU_K_init; + cache->cache_free = LRU_K_free; + cache->get = LRU_K_get; + cache->find = LRU_K_find; + cache->insert = LRU_K_insert; + cache->evict = LRU_K_evict; + cache->to_evict = LRU_K_to_evict; + cache->remove = LRU_K_remove; + + if (ccache_params.consider_obj_metadata) { + cache->obj_md_size = 8; + } else { + cache->obj_md_size = 0; + } + + LRU_K_parse_params(cache, cache_specific_params); + + return cache; +} + +/** + * free resources used by this cache + * + * @param cache + */ +static void LRU_K_free(cache_t *cache) { + delete reinterpret_cast(cache->eviction_params); + cache_struct_free(cache); +} + +/** + * @brief this function is the user facing API + * it performs the following logic + * + * ``` + * if obj in cache: + * update_metadata + * return true + * else: + * if cache does not have enough space: + * evict until it has space to insert + * insert the object + * return false + * ``` + * + * @param cache + * @param req + * @return true if cache hit, false if cache miss + */ +static bool LRU_K_get(cache_t *cache, const request_t *req) { + return cache_get_base(cache, req); +} + +// *********************************************************************** +// **** **** +// **** developer facing APIs (used by cache developer) **** +// **** **** +// *********************************************************************** + +/** + * @brief find an object in the cache + * + * @param cache + * @param req + * @param update_cache whether to update the cache metadata + * @return the object or NULL if not found + */ +static cache_obj_t *LRU_K_find(cache_t *cache, const request_t *req, + bool update_cache) { + auto *lruk = + reinterpret_cast(cache->eviction_params); + cache_obj_t *obj = cache_find_base(cache, req, update_cache); + + if (obj != nullptr && update_cache) { + int64_t vtime = cache->n_req; + auto &hist = lruk->history[obj]; + + /* Add the current access to history and maintain window of size K */ + hist.push_back(vtime); + if ((int)hist.size() > lruk->k) { + hist.pop_front(); + } + + bool in_fifo = (lruk->fifo_map.count(obj) > 0); + + if (in_fifo && (int)hist.size() >= lruk->k) { + /* Object has accumulated K accesses: graduate from FIFO to PQ */ + lruk->fifo_queue.erase(lruk->fifo_map[obj]); + lruk->fifo_map.erase(obj); + + int64_t kth_vtime = hist.front(); + obj_id_t oid = obj->obj_id; + lruk->pq.insert({kth_vtime, oid}); + lruk->pq_map[obj] = kth_vtime; + } else if (!in_fifo) { + /* Object is already in PQ: update its K-th vtime priority */ + int64_t old_kth = lruk->pq_map[obj]; + obj_id_t oid = obj->obj_id; + lruk->pq.erase({old_kth, oid}); + int64_t new_kth = hist.front(); + lruk->pq.insert({new_kth, oid}); + lruk->pq_map[obj] = new_kth; + } + /* else: still in FIFO with < K accesses, no PQ update needed */ + } + + return obj; +} + +/** + * @brief insert an object into the cache. + * Assumes the cache has enough space; eviction should be performed before + * calling this function. + * + * @param cache + * @param req + * @return the inserted object + */ +static cache_obj_t *LRU_K_insert(cache_t *cache, const request_t *req) { + auto *lruk = + reinterpret_cast(cache->eviction_params); + + cache_obj_t *obj = cache_insert_base(cache, req); + + int64_t vtime = cache->n_req; + lruk->history[obj] = {vtime}; + + if (lruk->k == 1) { + /* K=1: every object immediately enters the PQ (equivalent to LRU) */ + obj_id_t oid = obj->obj_id; + lruk->pq.insert({vtime, oid}); + lruk->pq_map[obj] = vtime; + } else { + /* Objects with < K accesses go to the FIFO queue */ + lruk->fifo_queue.push_back(obj); + lruk->fifo_map[obj] = std::prev(lruk->fifo_queue.end()); + } + + return obj; +} + +/** + * @brief find the object to be evicted without actually evicting it + * + * @param cache the cache + * @return the object to be evicted + */ +static cache_obj_t *LRU_K_to_evict(cache_t *cache, const request_t *req) { + auto *lruk = + reinterpret_cast(cache->eviction_params); + + if (!lruk->fifo_queue.empty()) { + return lruk->fifo_queue.front(); + } + if (!lruk->pq.empty()) { + /* pq is ordered by (kth_vtime, obj_id) ascending: the front has the + * smallest K-th access vtime, which corresponds to the oldest K-th + * access and therefore the largest backward K-distance */ + obj_id_t evict_id = lruk->pq.begin()->second; + return hashtable_find_obj_id(cache->hashtable, evict_id); + } + return nullptr; +} + +/** + * @brief evict an object from the cache + * + * @param cache + * @param req not used + */ +static void LRU_K_evict(cache_t *cache, const request_t *req) { + auto *lruk = + reinterpret_cast(cache->eviction_params); + + cache_obj_t *obj; + + if (!lruk->fifo_queue.empty()) { + /* Evict from FIFO queue first (infinite backward K-distance) */ + obj = lruk->fifo_queue.front(); + lruk->fifo_queue.pop_front(); + lruk->fifo_map.erase(obj); + } else { + /* Evict from PQ: smallest K-th vtime = largest backward K-distance */ + DEBUG_ASSERT(!lruk->pq.empty()); + auto it = lruk->pq.begin(); + obj_id_t evict_id = it->second; + lruk->pq.erase(it); + obj = hashtable_find_obj_id(cache->hashtable, evict_id); + DEBUG_ASSERT(obj != nullptr); + lruk->pq_map.erase(obj); + } + + lruk->history.erase(obj); + cache_remove_obj_base(cache, obj, true); +} + +/** + * @brief remove an object from the cache by user request + * + * @param cache + * @param obj_id + * @return true if removed, false if not found + */ +static bool LRU_K_remove(cache_t *cache, obj_id_t obj_id) { + auto *lruk = + reinterpret_cast(cache->eviction_params); + + cache_obj_t *obj = hashtable_find_obj_id(cache->hashtable, obj_id); + if (obj == nullptr) { + return false; + } + + lruk->history.erase(obj); + + auto fifo_it = lruk->fifo_map.find(obj); + if (fifo_it != lruk->fifo_map.end()) { + lruk->fifo_queue.erase(fifo_it->second); + lruk->fifo_map.erase(fifo_it); + } else { + auto pq_it = lruk->pq_map.find(obj); + if (pq_it != lruk->pq_map.end()) { + int64_t kth_vtime = pq_it->second; + lruk->pq.erase({kth_vtime, obj_id}); + lruk->pq_map.erase(pq_it); + } + } + + cache_remove_obj_base(cache, obj, true); + return true; +} + +#ifdef __cplusplus +} +#endif diff --git a/libCacheSim/include/libCacheSim/evictionAlgo.h b/libCacheSim/include/libCacheSim/evictionAlgo.h index ffea3ff2..5996a12d 100644 --- a/libCacheSim/include/libCacheSim/evictionAlgo.h +++ b/libCacheSim/include/libCacheSim/evictionAlgo.h @@ -110,6 +110,9 @@ cache_t *LRU_Prob_init(const common_cache_params_t ccache_params, cache_t *LRU_init(const common_cache_params_t ccache_params, const char *cache_specific_params); +cache_t *LRU_K_init(const common_cache_params_t ccache_params, + const char *cache_specific_params); + cache_t *LRUv0_init(const common_cache_params_t ccache_params, const char *cache_specific_params); diff --git a/test/common.h b/test/common.h index 7bb66289..3973dd6f 100644 --- a/test/common.h +++ b/test/common.h @@ -227,10 +227,8 @@ static cache_t *create_test_cache(const char *alg_name, cache = Random_init(cc_params, NULL); } else if (strcasecmp(alg_name, "MRU") == 0) { cache = MRU_init(cc_params, NULL); - // } else if (strcmp(alg_name, "LRU_K") == 0) { - // cache = LRU_K_init(cc_params, NULL); - } else if (strcasecmp(alg_name, "LFU") == 0) { - cache = LFU_init(cc_params, NULL); + } else if (strcasecmp(alg_name, "LRU_K") == 0) { + cache = LRU_K_init(cc_params, NULL); } else if (strcasecmp(alg_name, "LFU") == 0) { cache = LFU_init(cc_params, NULL); } else if (strcasecmp(alg_name, "LFUDA") == 0) { diff --git a/test/test_evictionAlgo.c b/test/test_evictionAlgo.c index 565e098a..e3e01390 100644 --- a/test/test_evictionAlgo.c +++ b/test/test_evictionAlgo.c @@ -140,6 +140,13 @@ static const cache_test_data_t test_data_truth[] = { .miss_cnt_true = {93374, 89783, 83572, 81722, 72494, 72104, 71972, 71704}, .miss_byte_true = {4214303232, 4061242368, 3778040320, 3660569600, 3100927488, 3078128640, 3075403776, 3061662720}}, + {.cache_name = "LRU_K", + .hashpower = 20, + .req_cnt_true = 113872, + .req_byte_true = 4368040448, + .miss_cnt_true = {91699, 86720, 78578, 76707, 69945, 66221, 64445, 64376}, + .miss_byte_true = {4158632960, 3917211648, 3536227840, 3455379968, + 3035580416, 2801699328, 2699456000, 2696345600}}, {.cache_name = "MRU", .hashpower = 20, .req_cnt_true = 113872, @@ -342,41 +349,45 @@ static void test_LRU(gconstpointer user_data) { test_cache_algorithm(user_data, &test_data_truth[16]); } -static void test_MRU(gconstpointer user_data) { +static void test_LRU_K(gconstpointer user_data) { test_cache_algorithm(user_data, &test_data_truth[17]); } -static void test_QDLP_FIFO(gconstpointer user_data) { +static void test_MRU(gconstpointer user_data) { test_cache_algorithm(user_data, &test_data_truth[18]); } -static void test_Random(gconstpointer user_data) { +static void test_QDLP_FIFO(gconstpointer user_data) { test_cache_algorithm(user_data, &test_data_truth[19]); } -static void test_S3FIFO(gconstpointer user_data) { +static void test_Random(gconstpointer user_data) { test_cache_algorithm(user_data, &test_data_truth[20]); } -static void test_S3FIFOv0(gconstpointer user_data) { +static void test_S3FIFO(gconstpointer user_data) { test_cache_algorithm(user_data, &test_data_truth[21]); } -static void test_Sieve(gconstpointer user_data) { +static void test_S3FIFOv0(gconstpointer user_data) { test_cache_algorithm(user_data, &test_data_truth[22]); } -static void test_SLRU(gconstpointer user_data) { +static void test_Sieve(gconstpointer user_data) { test_cache_algorithm(user_data, &test_data_truth[23]); } -static void test_SR_LRU(gconstpointer user_data) { +static void test_SLRU(gconstpointer user_data) { test_cache_algorithm(user_data, &test_data_truth[24]); } +static void test_SR_LRU(gconstpointer user_data) { + test_cache_algorithm(user_data, &test_data_truth[25]); +} + #if defined(ENABLE_3L_CACHE) && ENABLE_3L_CACHE == 1 static void test_3LCache(gconstpointer user_data) { - test_cache_algorithm(user_data, &test_data_truth[25]); + test_cache_algorithm(user_data, &test_data_truth[26]); } #endif /* ENABLE_3L_CACHE */ @@ -419,6 +430,7 @@ int main(int argc, char *argv[]) { g_test_add_data_func("/libCacheSim/cacheAlgo_LHD", reader, test_LHD); g_test_add_data_func("/libCacheSim/cacheAlgo_LIRS", reader, test_LIRS); g_test_add_data_func("/libCacheSim/cacheAlgo_LRU", reader, test_LRU); + g_test_add_data_func("/libCacheSim/cacheAlgo_LRU_K", reader, test_LRU_K); g_test_add_data_func("/libCacheSim/cacheAlgo_MRU", reader, test_MRU); g_test_add_data_func("/libCacheSim/cacheAlgo_QDLP_FIFO", reader, test_QDLP_FIFO);