diff --git a/CMakeLists.txt b/CMakeLists.txt index 2eba5b37..20f1bd0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -287,6 +287,14 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/libCacheSim/cache/eviction/belady) ) endif() +if(EXISTS ${PROJECT_SOURCE_DIR}/libCacheSim/cache/eviction/BeladyOnline) + file(GLOB BeladyOnline_source + ${PROJECT_SOURCE_DIR}/libCacheSim/cache/eviction/BeladyOnline/*.cpp) + set(cache_source + ${cache_source} ${BeladyOnline_source} + ) +endif() + if(EXISTS ${PROJECT_SOURCE_DIR}/libCacheSim/cache/eviction/priv) file(GLOB priv_source ${PROJECT_SOURCE_DIR}/libCacheSim/cache/eviction/priv/*.c) diff --git a/libCacheSim/bin/cachesim/cache_init.h b/libCacheSim/bin/cachesim/cache_init.h index 34ab7335..9ec2f633 100644 --- a/libCacheSim/bin/cachesim/cache_init.h +++ b/libCacheSim/bin/cachesim/cache_init.h @@ -90,6 +90,8 @@ static inline cache_t *create_cache(const char *trace_path, const char *eviction exit(1); } cache = Belady_init(cc_params, eviction_params); + } else if (strcasecmp(eviction_algo, "beladyOnline") == 0) { + cache = BeladyOnline_init(cc_params, eviction_params); } else if (strcasecmp(eviction_algo, "nop") == 0) { cache = nop_init(cc_params, eviction_params); } else if (strcasecmp(eviction_algo, "beladySize") == 0) { diff --git a/libCacheSim/cache/eviction/BeladyOnline/BeladyOnline.cpp b/libCacheSim/cache/eviction/BeladyOnline/BeladyOnline.cpp new file mode 100644 index 00000000..defc3c2e --- /dev/null +++ b/libCacheSim/cache/eviction/BeladyOnline/BeladyOnline.cpp @@ -0,0 +1,228 @@ +// the online version of Belady eviction algorithm (MIN) +// the implementation is inspired by the paper "Jain A, Lin C. Back to the future: Leveraging Belady's algorithm for improved cache replacement[J]. ACM SIGARCH Computer Architecture News, 2016, 44(3): 78-89." +// +// BeladyOnline.c +// libCacheSim +// +// +// Created by Xiaojun Guo on 3/27/25. +// + +#include "../../../dataStructure/hashtable/hashtable.h" +#include "../../../include/libCacheSim/evictionAlgo.h" +#include "./BeladyOnline_wrapper.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// #define EVICT_IMMEDIATELY_IF_NO_FUTURE_ACCESS 1 + +// *********************************************************************** +// **** **** +// **** function declarations **** +// **** **** +// *********************************************************************** + +static void BeladyOnline_free(cache_t *cache); +static bool BeladyOnline_get(cache_t *cache, const request_t *req); +static cache_obj_t *BeladyOnline_find(cache_t *cache, const request_t *req, + const bool update_cache); +static cache_obj_t *BeladyOnline_insert(cache_t *cache, const request_t *req); +static cache_obj_t *BeladyOnline_to_evict(cache_t *cache, const request_t *req); +static void BeladyOnline_evict(cache_t *cache, const request_t *req); +static bool BeladyOnline_remove(cache_t *cache, const obj_id_t obj_id); +static void BeladyOnline_remove_obj(cache_t *cache, cache_obj_t *obj); + +// *********************************************************************** +// **** **** +// **** end user facing functions **** +// **** **** +// **** init, free, get **** +// *********************************************************************** + +/** + * @brief initialize a BeladyOnline cache + * + * @param ccache_params some common cache parameters + * @param cache_specific_params BeladyOnline specific parameters, should be NULL + */ +cache_t *BeladyOnline_init(const common_cache_params_t ccache_params, + __attribute__((unused)) + const char *cache_specific_params) { + cache_t *cache = cache_struct_init("BeladyOnline", ccache_params, cache_specific_params); + cache->cache_init = BeladyOnline_init; + cache->cache_free = BeladyOnline_free; + cache->get = BeladyOnline_get; + cache->find = BeladyOnline_find; + cache->insert = BeladyOnline_insert; + cache->evict = BeladyOnline_evict; + cache->to_evict = BeladyOnline_to_evict; + cache->remove = BeladyOnline_remove; + + BeladyOnline_t *params = BeladyOnline_new(ccache_params.cache_size); + cache->eviction_params = params; + + return cache; +} + +/** + * free resources used by this cache + * + * @param cache + */ +static void BeladyOnline_free(cache_t *cache) { + BeladyOnline_t *params = (BeladyOnline_t *)cache->eviction_params; + BeladyOnline_delete(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 + * ``` + * + * For BeladyOnline, it performs different logic from other eviction algorithms. + * BealdyOnline uses a segment tree to record the occupancy of each time slot, + * and when the object is accessed for the second time, BealdyOnline determines + * whether it was previously in the cache. + * + * ``` + * for obj in trace: + * if(max(occ_vec[obj.last_access_vtime, current_vtime]) + obj.size <= cache_size): + * the object should be inserted into the cache at its last access_vtime + * occ_vec[obj.last_access_vtime, current_vtime] += obj.size + * return true + * else: + * return false + * ``` + * + * + * @param cache + * @param req + * @return true if cache hit, false if cache miss + */ +static bool BeladyOnline_get(cache_t *cache, const request_t *req) { + BeladyOnline_t *params = (BeladyOnline_t *)cache->eviction_params; + + return _BeladyOnline_get(params, req->obj_id, req->obj_size + cache->obj_md_size); +} + +// *********************************************************************** +// **** **** +// **** developer facing APIs (used by cache developer) **** +// **** **** +// *********************************************************************** + +/** + * @brief find an object in the cache + * + * BeladyOnline doesn't support this function + * + * @param cache + * @param req + * @param update_cache whether to update the cache, + * if true, the object is promoted + * and if the object is expired, it is removed from the cache + * @return the object or NULL if not found + */ +static cache_obj_t *BeladyOnline_find(cache_t *cache, const request_t *req, + const bool update_cache) { + assert(false); + + return NULL; +} + +/** + * @brief insert an object into the cache, + * update the hash table and cache metadata + * this function assumes the cache has enough space + * and eviction is not part of this function + * + * BeladyOnline doesn't support this function + * + * @param cache + * @param req + * @return the inserted object + */ +static cache_obj_t *BeladyOnline_insert(cache_t *cache, const request_t *req) { + assert(false); + + return NULL; +} + +/** + * @brief find the object to be evicted + * this function does not actually evict the object or update metadata + * not all eviction algorithms support this function + * because the eviction logic cannot be decoupled from finding eviction + * candidate, so use assert(false) if you cannot support this function + * + * BeladyOnline doesn't support this function + * + * @param cache the cache + * @return the object to be evicted + */ +static cache_obj_t *BeladyOnline_to_evict(cache_t *cache, __attribute__((unused)) + const request_t *req) { + assert(false); + return NULL; +} + +/** + * @brief evict an object from the cache + * it needs to call cache_evict_base before returning + * which updates some metadata such as n_obj, occupied size, and hash table + * + * BeladyOnline doesn't support this function + * + * @param cache + * @param req not used + */ +static void BeladyOnline_evict(cache_t *cache, + __attribute__((unused)) const request_t *req) { + assert(false); +} + + +/** + * BeladyOnline doesn't support this function + */ +static void BeladyOnline_remove_obj(cache_t *cache, cache_obj_t *obj) { + assert(false); +} + +/** + * @brief remove an object from the cache + * this is different from cache_evict because it is used to for user trigger + * remove, and eviction is used by the cache to make space for new objects + * + * it needs to call cache_remove_obj_base before returning + * which updates some metadata such as n_obj, occupied size, and hash table + * + * BeladyOnline doesn't support this function + * + * @param cache + * @param obj_id + * @return true if the object is removed, false if the object is not in the + * cache + */ +static bool BeladyOnline_remove(cache_t *cache, const obj_id_t obj_id) { + assert(false); + return false; +} + +#ifdef __cplusplus +} +#endif diff --git a/libCacheSim/cache/eviction/BeladyOnline/BeladyOnline_impl.cpp b/libCacheSim/cache/eviction/BeladyOnline/BeladyOnline_impl.cpp new file mode 100644 index 00000000..3655cb09 --- /dev/null +++ b/libCacheSim/cache/eviction/BeladyOnline/BeladyOnline_impl.cpp @@ -0,0 +1,56 @@ +// created by Xiaojun Guo, 02/28/25 + +#include +#include "../../../dataStructure/segment_tree.hpp" + +class BeladyOnline { + int64_t cache_size_; + SegmentTree tree; + unordered_map last_access_vtime_; + int64_t v_time_; + +public: + BeladyOnline(int64_t cache_size) { + cache_size_ = cache_size; + v_time_ = 0; + } + + + bool BeladyOnline_get(int64_t obj_id, int64_t size){ + bool ret = false; + bool accessed_before = false; + int64_t last_access_vtime = 0; + + /* 1. get the last access time */ + if(last_access_vtime_.count(obj_id)){ + accessed_before = true; + last_access_vtime = last_access_vtime_[obj_id]; + } + + + /* 2. decide whether it is a hit */ + if(accessed_before){ + int64_t current_occ = tree.query(last_access_vtime, v_time_); + if((current_occ + size) <= cache_size_){ + tree.update(last_access_vtime, v_time_, size); + ret = true; + } + } + + /* 3. update the last access time */ + last_access_vtime_[obj_id] = v_time_; + + /* 4. update the v_time_ */ + v_time_ ++; + + return ret; + } + +}; + +extern "C" { + + BeladyOnline* BeladyOnline_new(int64_t cache_size) { return new BeladyOnline(cache_size); } + bool _BeladyOnline_get(BeladyOnline* obj, int64_t obj_id, int64_t size) { return obj->BeladyOnline_get(obj_id, size); } + void BeladyOnline_delete(BeladyOnline* obj) { delete obj; } +} \ No newline at end of file diff --git a/libCacheSim/cache/eviction/BeladyOnline/BeladyOnline_wrapper.h b/libCacheSim/cache/eviction/BeladyOnline/BeladyOnline_wrapper.h new file mode 100644 index 00000000..66b1f594 --- /dev/null +++ b/libCacheSim/cache/eviction/BeladyOnline/BeladyOnline_wrapper.h @@ -0,0 +1,19 @@ +#ifndef BELADYONLINE_WRAPPER_H +#define BELADYONLINE_WRAPPER_H +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct BeladyOnline BeladyOnline_t; + +BeladyOnline_t* BeladyOnline_new(int64_t cache_size); +bool _BeladyOnline_get(BeladyOnline_t* obj, int64_t obj_id, int64_t size); +void BeladyOnline_delete(BeladyOnline_t* obj); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/libCacheSim/cache/eviction/CMakeLists.txt b/libCacheSim/cache/eviction/CMakeLists.txt index 3b014166..615c6a4b 100644 --- a/libCacheSim/cache/eviction/CMakeLists.txt +++ b/libCacheSim/cache/eviction/CMakeLists.txt @@ -81,6 +81,8 @@ set(sourceCPP cpp/GDSF.cpp LHD/lhd.cpp LHD/LHD_Interface.cpp + BeladyOnline/BeladyOnline_impl.cpp + BeladyOnline/BeladyOnline.cpp ) if (ENABLE_LRB) @@ -113,6 +115,7 @@ if (ENABLE_GLCACHE) endif() + add_library (evictionC ${sourceC}) target_link_libraries(evictionC cachelib dataStructure utils) add_library (evictionCPP ${sourceCPP}) diff --git a/libCacheSim/dataStructure/segment_tree.hpp b/libCacheSim/dataStructure/segment_tree.hpp new file mode 100644 index 00000000..ce614c63 --- /dev/null +++ b/libCacheSim/dataStructure/segment_tree.hpp @@ -0,0 +1,109 @@ +// created by Xiaojun Guo, 03/03/25. Currently used for online Belady implementation + +#ifndef SEGMENT_TREE_HPP +#define SEGMENT_TREE_HPP +#include +#include +#include +#include + +using namespace std; + +class SegmentTree { +private: + vector tree, lazy; + int size; + + int get_left(int node, int granularity) { + return node - granularity; + } + + int get_right(int node, int granularity) { + return node - 1; + } + + int get_root(){ + return 2*size - 1; + } + + void updateRange(int node, int start, int end, int l, int r, int64_t val) { + int64_t granularity = end - start + 1; + if(lazy[node] != 0) { + tree[node] += lazy[node]; + if(start != end) { + lazy[get_left(node, granularity)] += lazy[node]; + lazy[get_right(node, granularity)] += lazy[node]; + } + lazy[node] = 0; + } + if(start > end || start > r || end < l) + return; + if(start >= l && end <= r) { + tree[node] += val; + if(start != end) { + lazy[get_left(node, granularity)] += val; + lazy[get_right(node, granularity)] += val; + } + return; + } + int mid = (start + end) / 2; + updateRange(get_left(node, granularity), start, mid, l, r, val); + updateRange(get_right(node, granularity), mid + 1, end, l, r, val); + tree[node] = max(tree[get_left(node, granularity)], tree[get_right(node, granularity)]); + } + + int64_t queryRange(int node, int start, int end, int l, int r) { + if(start > end || start > r || end < l) + return 0; + int64_t granularity = end - start + 1; + if(lazy[node] != 0) { + tree[node] += lazy[node]; + if(start != end) { + lazy[get_left(node, granularity)] += lazy[node]; + lazy[get_right(node, granularity)] += lazy[node]; + } + lazy[node] = 0; + } + if(start >= l && end <= r) + return tree[node]; + int mid = (start + end) / 2; + int64_t p1 = queryRange(get_left(node, granularity), start, mid, l, r); + int64_t p2 = queryRange(get_right(node, granularity), mid + 1, end, l, r); + return max(p1, p2); + } + +public: + SegmentTree() { + int initial_size = 1; + tree.resize(initial_size * 2, 0); + lazy.resize(initial_size * 2, 0); + size = initial_size; + } + + void double_size() { + int old_max = tree[get_root()]; + size *= 2; + tree.resize(size * 2, 0); + lazy.resize(size * 2, 0); + tree[get_root()] = old_max; + } + + void resize_to(int new_size) { + while(size < new_size) + double_size(); + } + + void update(int l, int r, int64_t val) { + if(r >= size){ + resize_to(r + 1); + } + + updateRange(get_root(), 0, size-1, l, r, val); + } + + int64_t query(int l, int r) { + return queryRange(get_root(), 0, size-1, l, r); + } +}; + +#endif \ No newline at end of file diff --git a/libCacheSim/include/libCacheSim/evictionAlgo.h b/libCacheSim/include/libCacheSim/evictionAlgo.h index efc29a70..4bbb1818 100644 --- a/libCacheSim/include/libCacheSim/evictionAlgo.h +++ b/libCacheSim/include/libCacheSim/evictionAlgo.h @@ -44,6 +44,8 @@ cache_t *ARCv0_init(const common_cache_params_t ccache_params, const char *cache cache_t *Belady_init(const common_cache_params_t ccache_params, const char *cache_specific_params); +cache_t *BeladyOnline_init(const common_cache_params_t ccache_params, const char *cache_specific_params); + cache_t *BeladySize_init(const common_cache_params_t ccache_params, const char *cache_specific_params); cache_t *Cacheus_init(const common_cache_params_t ccache_params, const char *cache_specific_params); diff --git a/test/common.h b/test/common.h index 10e9ed97..a1b05bd4 100644 --- a/test/common.h +++ b/test/common.h @@ -209,6 +209,8 @@ static cache_t *create_test_cache(const char *alg_name, common_cache_params_t cc cache = ClockPro_init(cc_params, NULL); } else if (strcasecmp(alg_name, "Belady") == 0) { cache = Belady_init(cc_params, NULL); + } else if (strcasecmp(alg_name, "BeladyOnline") == 0) { + cache = BeladyOnline_init(cc_params, NULL); } else if (strcasecmp(alg_name, "BeladySize") == 0) { cache = BeladySize_init(cc_params, NULL); } else if (strcasecmp(alg_name, "LRUv0") == 0) { diff --git a/test/test_evictionAlgo.c b/test/test_evictionAlgo.c index ee87fa21..e7fd3dc3 100644 --- a/test/test_evictionAlgo.c +++ b/test/test_evictionAlgo.c @@ -131,6 +131,27 @@ static void test_Belady(gconstpointer user_data) { my_free(sizeof(cache_stat_t), res); } +static void test_BeladyOnline(gconstpointer user_data) { + /* the request byte is different from others because the oracleGeneral + * trace removes all object size changes (and use the size of last appearance + * of an object as the object size throughout the trace */ + uint64_t req_cnt_true = 113872, req_byte_true = 4368040448; + uint64_t miss_cnt_true[] = {79194, 70670, 65476, 61570, 59615, 57576, 50854, 48974}; + uint64_t miss_byte_true[] = {3472415232, 2995094528, 2726659584, 2537637888, + 2403420160, 2269202432, 2134984704, 2029769728}; + + reader_t *reader = (reader_t *)user_data; + common_cache_params_t cc_params = {.cache_size = CACHE_SIZE, .hashpower = 20, .default_ttl = DEFAULT_TTL}; + cache_t *cache = create_test_cache("BeladyOnline", cc_params, reader, NULL); + g_assert_true(cache != NULL); + cache_stat_t *res = simulate_at_multi_sizes_with_step_size(reader, cache, STEP_SIZE, NULL, 0, 0, _n_cores(), false); + + print_results(cache, res); + _verify_profiler_results(res, CACHE_SIZE / STEP_SIZE, g_req_cnt_true, miss_cnt_true, g_req_byte_true, miss_byte_true); + cache->cache_free(cache); + my_free(sizeof(cache_stat_t), res); +} + static void test_BeladySize(gconstpointer user_data) { /* the request byte is different from others because the oracleGeneral * trace removes all object size changes (and use the size of last appearance @@ -494,6 +515,7 @@ int main(int argc, char *argv[]) { // reader = setup_vscsi_reader(); reader = setup_oracleGeneralBin_reader(); + g_test_add_data_func("/libCacheSim/cacheAlgo_BeladyOnline", reader, test_BeladyOnline); g_test_add_data_func("/libCacheSim/cacheAlgo_Sieve", reader, test_Sieve); g_test_add_data_func("/libCacheSim/cacheAlgo_S3FIFO", reader, test_S3FIFO); @@ -521,6 +543,7 @@ int main(int argc, char *argv[]) { g_test_add_data_func("/libCacheSim/cacheAlgo_LFUCpp", reader, test_LFUCpp); g_test_add_data_func("/libCacheSim/cacheAlgo_GDSF", reader, test_GDSF); g_test_add_data_func("/libCacheSim/cacheAlgo_LHD", reader, test_LHD); + // /* Belady requires reader that has next access information and can only use // * oracleGeneral trace */