diff --git a/libCacheSim/bin/cachesim/cache_init.h b/libCacheSim/bin/cachesim/cache_init.h index 52c7363b..897e0335 100644 --- a/libCacheSim/bin/cachesim/cache_init.h +++ b/libCacheSim/bin/cachesim/cache_init.h @@ -49,6 +49,7 @@ static inline cache_t *create_cache(const char *trace_path, {"flashProb", flashProb_init}, {"gdsf", GDSF_init}, {"lhd", LHD_init}, + {"lhd_compute", LHD_compute_init}, {"lecar", LeCaR_init}, {"lecarv0", LeCaRv0_init}, {"lfu", LFU_init}, @@ -146,6 +147,17 @@ static inline cache_t *create_cache(const char *trace_path, } cc_params.hashpower = MAX(cc_params.hashpower - 8, 16); cache = BeladySize_init(cc_params, eviction_params); + } else if (strcasecmp(eviction_algo, "beladyCompute") == 0) { + if (strcasestr(trace_path, "oracleGeneral") == NULL && + strcasestr(trace_path, "lcs") == NULL) { + WARN("beladyCompute is only supported for oracleGeneral and lcs trace\n"); + WARN("to convert a trace to lcs format\n"); + WARN("./bin/traceConv input_trace trace_format output_trace\n"); + WARN("./bin/traceConv ../data/cloudPhysicsIO.txt txt\n"); + exit(1); + } + cc_params.hashpower = MAX(cc_params.hashpower - 8, 16); + cache = BeladyCompute_init(cc_params, eviction_params); } else { ERROR("do not support algorithm %s\n", eviction_algo); abort(); diff --git a/libCacheSim/cache/CMakeLists.txt b/libCacheSim/cache/CMakeLists.txt index 7f44aded..b9f1230a 100644 --- a/libCacheSim/cache/CMakeLists.txt +++ b/libCacheSim/cache/CMakeLists.txt @@ -29,6 +29,7 @@ set(eviction_sources_c eviction/ARCv0.c # an inefficient version but easier to understand eviction/Belady.c eviction/BeladySize.c + eviction/BeladyCompute.c eviction/CAR.c eviction/Cacheus.c eviction/Clock.c @@ -51,6 +52,8 @@ set(eviction_sources_c eviction/RandomLRU.c eviction/RandomTwo.c eviction/S3FIFO.c + eviction/S3FIFOSize.c + eviction/S3FIFOCompute.c eviction/Sieve.c eviction/Size.c eviction/SLRU.c @@ -75,8 +78,11 @@ set(eviction_sources_c set(eviction_sources_cpp eviction/cpp/LFU.cpp eviction/cpp/GDSF.cpp + eviction/cpp/GDSF_compute.cpp eviction/LHD/lhd.cpp eviction/LHD/LHD_Interface.cpp + eviction/LHD/lhd_compute.cpp + eviction/LHD/LHD_compute_interface.cpp ) # LRB diff --git a/libCacheSim/cache/cache.c b/libCacheSim/cache/cache.c index 304fe14e..1ac3b24a 100644 --- a/libCacheSim/cache/cache.c +++ b/libCacheSim/cache/cache.c @@ -11,6 +11,26 @@ extern "C" { #endif +#ifdef RECORD_EVICTION_PROCESS +FILE *eviction_process_ofile = NULL; + +void set_new_record_eviction_process_file(const char *ofilepath) { + if (eviction_process_ofile != NULL) { + fclose(eviction_process_ofile); + } + eviction_process_ofile = fopen(ofilepath, "w"); + if (eviction_process_ofile == NULL) { + ERROR("cannot open eviction process file %s\n", ofilepath); + } +} + +void print_eviction_debug_message(const char *msg) { + if (eviction_process_ofile != NULL) { + fprintf(eviction_process_ofile, "%s\n", msg); + } +} +#endif /* RECORD_EVICTION_PROCESS */ + /** this file contains both base function, which should be called by all *eviction algorithms, and the queue related functions, which should be called *by algorithm that uses only one queue and needs to update the queue such as @@ -336,6 +356,11 @@ void cache_evict_base(cache_t *cache, cache_obj_t *obj, */ void cache_remove_obj_base(cache_t *cache, cache_obj_t *obj, bool remove_from_hashtable) { +#ifdef RECORD_EVICTION_PROCESS + if (eviction_process_ofile != NULL) { + fprintf(eviction_process_ofile, "%ld\n", (long)obj->obj_id); + } +#endif /* RECORD_EVICTION_PROCESS */ DEBUG_ASSERT(cache->occupied_byte >= obj->obj_size + cache->obj_md_size); cache->occupied_byte -= (obj->obj_size + cache->obj_md_size); cache->n_obj -= 1; diff --git a/libCacheSim/cache/eviction/BeladyCompute.c b/libCacheSim/cache/eviction/BeladyCompute.c new file mode 100644 index 00000000..b7f50596 --- /dev/null +++ b/libCacheSim/cache/eviction/BeladyCompute.c @@ -0,0 +1,364 @@ +// +// BeladyCompute.c +// libCacheSim +// +// sample object and compare reuse_distance / compute_intensity, then evict the greatest one +// compute_intensity is taken from req->features[0] +// +// + +#include +#include + +#include "dataStructure/hashtable/hashtable.h" +#include "libCacheSim/evictionAlgo.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// #define EXACT_Belady 1 +static const char *DEFAULT_PARAMS = "n-sample=128"; + +typedef struct { + // how many samples to take at each eviction + int n_sample; +} BeladyCompute_params_t; /* BeladyCompute parameters */ + +// *********************************************************************** +// **** **** +// **** function declarations **** +// **** **** +// *********************************************************************** + +static void BeladyCompute_parse_params(cache_t *cache, + const char *cache_specific_params); +static void BeladyCompute_free(cache_t *cache); +static bool BeladyCompute_get(cache_t *cache, const request_t *req); +static cache_obj_t *BeladyCompute_find(cache_t *cache, const request_t *req, + const bool update_cache); +static cache_obj_t *BeladyCompute_insert(cache_t *cache, const request_t *req); +static cache_obj_t *BeladyCompute_to_evict(cache_t *cache, const request_t *req); +static void BeladyCompute_evict(cache_t *cache, const request_t *req); +static bool BeladyCompute_remove(cache_t *cache, const obj_id_t obj_id); +static void BeladyCompute_remove_obj(cache_t *cache, cache_obj_t *obj); + +// *********************************************************************** +// **** **** +// **** end user facing functions **** +// **** **** +// **** init, free, get **** +// *********************************************************************** + +/** + * @brief initialize a BeladyCompute cache + * + * @param ccache_params some common cache parameters + * @param cache_specific_params BeladyCompute specific parameters, see parse_params + * function or use -e "print" with the cachesim binary + */ +cache_t *BeladyCompute_init(const common_cache_params_t ccache_params, + const char *cache_specific_params) { + cache_t *cache = + cache_struct_init("BeladyCompute", ccache_params, cache_specific_params); + + cache->cache_init = BeladyCompute_init; + cache->cache_free = BeladyCompute_free; + cache->get = BeladyCompute_get; + cache->find = BeladyCompute_find; + cache->insert = BeladyCompute_insert; + cache->evict = BeladyCompute_evict; + cache->remove = BeladyCompute_remove; + cache->to_evict = BeladyCompute_to_evict; + + BeladyCompute_params_t *params = + (BeladyCompute_params_t *)malloc(sizeof(BeladyCompute_params_t)); + cache->eviction_params = params; + + BeladyCompute_parse_params(cache, DEFAULT_PARAMS); + if (cache_specific_params != NULL) { + BeladyCompute_parse_params(cache, cache_specific_params); + } + + return cache; +} + +/** + * free resources used by this cache + * + * @param cache + */ +static void BeladyCompute_free(cache_t *cache) { + free(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 BeladyCompute_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, + * 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 *BeladyCompute_find(cache_t *cache, const request_t *req, + const bool update_cache) { + cache_obj_t *obj = hashtable_find_obj_id(cache->hashtable, req->obj_id); + if (obj != NULL && likely(update_cache)) { + // store the compute intensity in the cache object for eviction decisions + if (req->n_features > 0) { + obj->Belady.compute_intensity = req->features[0]; + } + + if (req->next_access_vtime == -1 || req->next_access_vtime == INT64_MAX) { + obj->Belady.next_access_vtime = -1; + } else { + obj->Belady.next_access_vtime = req->next_access_vtime; + } + } + + return obj; +} + +/** + * @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 + * + * @param cache + * @param req + * @return the inserted object + */ +static cache_obj_t *BeladyCompute_insert(cache_t *cache, const request_t *req) { + cache_obj_t *obj = cache_insert_base(cache, req); + + // store the compute intensity for eviction decisions + if (req->n_features > 0) { + obj->Belady.compute_intensity = req->features[0]; + } else { + obj->Belady.compute_intensity = 1; // default to 1 to avoid division by zero + } + + if (req->next_access_vtime == -1 || req->next_access_vtime == INT64_MAX) { + obj->Belady.next_access_vtime = -1; + } else { + obj->Belady.next_access_vtime = req->next_access_vtime; + } + + return obj; +} + +#ifdef EXACT_Belady +struct hash_iter_user_data { + int64_t curr_vtime; + cache_obj_t *to_evict_obj; + double max_score; +}; + +static void hashtable_iter_Belady_compute(cache_obj_t *cache_obj, + void *userdata) { + struct hash_iter_user_data *iter_userdata = + (struct hash_iter_user_data *)userdata; + if (iter_userdata->max_score == DBL_MAX) return; + + double obj_score; + if (cache_obj->Belady.next_access_vtime == -1) { + obj_score = DBL_MAX; + } else { + int64_t time_diff = cache_obj->Belady.next_access_vtime - iter_userdata->curr_vtime; + int32_t compute_intensity = cache_obj->Belady.compute_intensity; + if (compute_intensity <= 0) compute_intensity = 1; // avoid division by zero + obj_score = (double)time_diff / (double)compute_intensity; + } + + if (obj_score > iter_userdata->max_score) { + iter_userdata->to_evict_obj = cache_obj; + iter_userdata->max_score = obj_score; + } +} + +/** + * @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 + * + * @param cache the cache + * @return the object to be evicted + */ +static cache_obj_t *BeladyCompute_to_evict(cache_t *cache, const request_t *req) { + struct hash_iter_user_data iter_userdata; + iter_userdata.curr_vtime = cache->n_req; + iter_userdata.max_score = 0.0; + iter_userdata.to_evict_obj = NULL; + + hashtable_foreach(cache->hashtable, hashtable_iter_Belady_compute, + &iter_userdata); + + return iter_userdata.to_evict_obj; +} + +#else +static cache_obj_t *BeladyCompute_to_evict(cache_t *cache, const request_t *req) { + BeladyCompute_params_t *params = (BeladyCompute_params_t *)cache->eviction_params; + cache_obj_t *obj_to_evict = NULL, *sampled_obj; + double obj_to_evict_score = -DBL_MAX, sampled_obj_score = -1; + + for (int i = 0; i < params->n_sample; i++) { + sampled_obj = hashtable_rand_obj(cache->hashtable); + if (sampled_obj->Belady.next_access_vtime == -1) { + sampled_obj_score = DBL_MAX; + } else { + int64_t time_diff = sampled_obj->Belady.next_access_vtime - cache->n_req; + int32_t compute_intensity = sampled_obj->Belady.compute_intensity; + if (compute_intensity <= 0) compute_intensity = 1; // avoid division by zero + + if (time_diff <= 0) { + sampled_obj_score = -DBL_MAX / 2; + } else { + // Use log to handle large numbers and avoid overflow + sampled_obj_score = log((double)time_diff) - log((double)compute_intensity); + } + } + + if (obj_to_evict == NULL || obj_to_evict_score < sampled_obj_score) { + obj_to_evict = sampled_obj; + obj_to_evict_score = sampled_obj_score; + } + } + + if (obj_to_evict == NULL) { + WARN( + "BeladyCompute_to_evict: obj_to_evict is NULL, " + "maybe cache size is too small or hash power too large, " + "current hash table size %llu, n_obj %llu, cache size %lld, request " + "size " + "%lld, and %d samples " + "obj_to_evict_score %.4lf sampled_obj_score %.4lf\n", + (unsigned long long)hashsize(cache->hashtable->hashpower), + (unsigned long long)cache->get_n_obj(cache), + (long long)cache->cache_size, (long long)req->obj_size, + params->n_sample, obj_to_evict_score, sampled_obj_score); + return BeladyCompute_to_evict(cache, req); + } + + return obj_to_evict; +} +#endif + +/** + * @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 + * + * @param cache + * @param req not used + * @param evicted_obj if not NULL, return the evicted object to caller + */ +static void BeladyCompute_evict(cache_t *cache, const request_t *req) { + cache_obj_t *obj_to_evict = BeladyCompute_to_evict(cache, req); + cache_evict_base(cache, obj_to_evict, true); +} + +bool BeladyCompute_remove(cache_t *cache, const obj_id_t obj_id) { + cache_obj_t *obj = hashtable_find_obj_id(cache->hashtable, obj_id); + if (obj == NULL) { + return false; + } + cache_remove_obj_base(cache, obj, true); + return true; +} + +// *********************************************************************** +// **** **** +// **** parameter set up functions **** +// **** **** +// *********************************************************************** +/** + * @brief print the default parameters + * + */ +static const char *BeladyCompute_current_params(BeladyCompute_params_t *params) { + static __thread char params_str[128]; + snprintf(params_str, 128, "n-sample=%d\n", params->n_sample); + return params_str; +} + +/** + * parse the given parameters + * input parameter is a string, + * to see the default parameters, use current_params() + * or use -e "print" with cachesim + */ +static void BeladyCompute_parse_params(cache_t *cache, + const char *cache_specific_params) { + BeladyCompute_params_t *params = (BeladyCompute_params_t *)cache->eviction_params; + char *params_str = strdup(cache_specific_params); + char *old_params_str = params_str; + char *end; + + while (params_str != NULL && params_str[0] != '\0') { + /* different parameters are separated by comma, + * key and value are separated by '=' */ + char *key = strsep((char **)¶ms_str, "="); + char *value = strsep((char **)¶ms_str, ","); + + // skip the white space + while (params_str != NULL && *params_str == ' ') { + params_str++; + } + + if (strcasecmp(key, "n-sample") == 0) { + params->n_sample = (int)strtol(value, &end, 0); + if (strlen(end) > 2) { + ERROR("param parsing error, find string \"%s\" after number\n", end); + } + } else if (strcasecmp(key, "print") == 0) { + printf("current parameters: %s\n", BeladyCompute_current_params(params)); + exit(0); + } else { + ERROR("%s does not have parameter %s, support %s\n", cache->cache_name, + key, BeladyCompute_current_params(params)); + exit(1); + } + } + + free(old_params_str); +} + +#ifdef __cplusplus +} +#endif diff --git a/libCacheSim/cache/eviction/LHD/LHD_compute_interface.cpp b/libCacheSim/cache/eviction/LHD/LHD_compute_interface.cpp new file mode 100644 index 00000000..80e0553f --- /dev/null +++ b/libCacheSim/cache/eviction/LHD/LHD_compute_interface.cpp @@ -0,0 +1,338 @@ +#include + +#include "dataStructure/hashtable/hashtable.h" +#include "lhd_compute.hpp" +#include "repl.hpp" + +using namespace repl; + +#ifdef __cplusplus +extern "C" { +#endif + +// NOTE: Since LHD uses internal data struct to reprensent cache_obj_t, to suit +// the interface, we use a dummy pointer to represent the cache_obj_t. +// Specifically, for find and insert, we return a dummy pointer when the object +// is found or inserted and NULL when the object is not found. +static cache_obj_t *const DUMMY_CACHE_OBJ_PTR = + reinterpret_cast(1); + +typedef struct { + void *LHD_compute_cache; + + int associativity; + int admission; + + candidate_t to_evict_candidate; +} LHD_compute_params_t; + +// *********************************************************************** +// **** **** +// **** function declarations **** +// **** **** +// *********************************************************************** + +static void LHD_compute_free(cache_t *cache); +static bool LHD_compute_get(cache_t *cache, const request_t *req); + +static cache_obj_t *LHD_compute_find(cache_t *cache, const request_t *req, + const bool update_cache); +static cache_obj_t *LHD_compute_insert(cache_t *cache, const request_t *req); +static cache_obj_t *LHD_compute_to_evict(cache_t *cache, const request_t *req); +static void LHD_compute_evict(cache_t *cache, const request_t *req); +static bool LHD_compute_remove(cache_t *cache, const obj_id_t obj_id); +static int64_t LHD_compute_get_occupied_byte(const cache_t *cache); +static int64_t LHD_compute_get_n_obj(const cache_t *cache); + +// *********************************************************************** +// **** **** +// **** end user facing functions **** +// **** **** +// **** init, free, get **** +// *********************************************************************** + +/** + * @brief initialize the cache + * + * @param ccache_params some common cache parameters + * @param cache_specific_params cache specific parameters, see parse_params + * function or use -e "print" with the cachesim binary + */ +cache_t *LHD_compute_init(const common_cache_params_t ccache_params, + const char *cache_specific_params) { +#ifdef SUPPORT_TTL + if (ccache_params.default_ttl < 30 * 86400) { + ERROR("LHD_compute does not support expiration\n"); + abort(); + } +#endif + + cache_t *cache = + cache_struct_init("LHD_compute", ccache_params, cache_specific_params); + cache->cache_init = LHD_compute_init; + cache->cache_free = LHD_compute_free; + cache->get = LHD_compute_get; + cache->find = LHD_compute_find; + cache->insert = LHD_compute_insert; + cache->evict = LHD_compute_evict; + cache->to_evict = LHD_compute_to_evict; + cache->remove = LHD_compute_remove; + cache->can_insert = cache_can_insert_default; + cache->get_occupied_byte = LHD_compute_get_occupied_byte; + cache->get_n_obj = LHD_compute_get_n_obj; + cache->to_evict_candidate = + static_cast(malloc(sizeof(cache_obj_t))); + + if (ccache_params.consider_obj_metadata) { + cache->obj_md_size = 8 * 3 + 1; // two age, one time stamp + } else { + cache->obj_md_size = 0; + } + + auto *params = my_malloc(LHD_compute_params_t); + memset(params, 0, sizeof(LHD_compute_params_t)); + cache->eviction_params = params; + + if (params->admission == 0) { + params->admission = 8; + } + if (params->associativity == 0) { + params->associativity = 32; + } + + params->LHD_compute_cache = static_cast( + new LHDCompute(params->associativity, params->admission, cache)); + + return cache; +} + +/** + * free resources used by this cache + * + * @param cache + */ +static void LHD_compute_free(cache_t *cache) { + auto *params = static_cast(cache->eviction_params); + auto *lhd = static_cast(params->LHD_compute_cache); + delete lhd; + free(cache->to_evict_candidate); + my_free(sizeof(LHD_compute_params_t), 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 LHD_compute_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, + * 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 *LHD_compute_find(cache_t *cache, const request_t *req, + const bool update_cache) { + auto *params = static_cast(cache->eviction_params); + auto *lhd = static_cast(params->LHD_compute_cache); + + auto id = repl::candidate_t::make(req); + auto itr = lhd->sizeMap.find(id); + bool hit = (itr != lhd->sizeMap.end()); + + if (!hit) { + return NULL; + } + + if (update_cache) { + if (itr->second != req->obj_size) { + cache->occupied_byte -= itr->second; + cache->occupied_byte += req->obj_size; + itr->second = req->obj_size; + } + lhd->update(id, req); + } + + return DUMMY_CACHE_OBJ_PTR; +} + +/** + * @brief insert an object into the cache, + * update the hash table and cache metadata + * this function 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 *LHD_compute_insert(cache_t *cache, const request_t *req) { + auto *params = static_cast(cache->eviction_params); + auto *lhd = static_cast(params->LHD_compute_cache); + auto id = repl::candidate_t::make(req); + +#if defined(TRACK_EVICTION_V_AGE) + id.create_time = CURR_TIME(cache, req); +#endif + + lhd->sizeMap[id] = req->obj_size; + lhd->update(id, req); + + cache->occupied_byte += req->obj_size + cache->obj_md_size; + cache->n_obj += 1; + + return DUMMY_CACHE_OBJ_PTR; +} + +/** + * @brief find an eviction candidate, but do not evict from the cache, + * and do not update the cache metadata + * note that eviction must evicts this object, so if we implement this function + * and it uses random number, we must make sure that the same object is evicted + * when we call evict + * + * @param cache + * @param req + * @return cache_obj_t* + */ +static cache_obj_t *LHD_compute_to_evict(cache_t *cache, const request_t *req) { + auto *params = static_cast(cache->eviction_params); + auto *lhd = static_cast(params->LHD_compute_cache); + + cache->to_evict_candidate_gen_vtime = cache->n_req; + + repl::candidate_t victim = lhd->rank(req); + auto victimItr = lhd->sizeMap.find(victim); + assert(victimItr != lhd->sizeMap.end()); + + params->to_evict_candidate = victim; + + cache->to_evict_candidate->obj_id = victim.id; + cache->to_evict_candidate->obj_size = victimItr->second; + + return cache->to_evict_candidate; +} + +/** + * @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 + * + * @param cache + * @param req not used + * @param evicted_obj if not NULL, return the evicted object to caller + */ +static void LHD_compute_evict(cache_t *cache, const request_t *req) { + auto *params = static_cast(cache->eviction_params); + auto *lhd = static_cast(params->LHD_compute_cache); + + repl::candidate_t victim; + if (cache->to_evict_candidate_gen_vtime == cache->n_req) { + victim = params->to_evict_candidate; + cache->to_evict_candidate_gen_vtime = -1; + } else { + victim = lhd->rank(req); + } + + auto victimItr = lhd->sizeMap.find(victim); + if (victimItr == lhd->sizeMap.end()) { + std::cerr << "Couldn't find victim: " << victim << std::endl; + } + assert(victimItr != lhd->sizeMap.end()); + + DEBUG_ASSERT(cache->occupied_byte >= victimItr->second); + cache->occupied_byte -= (victimItr->second + cache->obj_md_size); + cache->n_obj -= 1; + +#if defined(TRACK_EVICTION_V_AGE) + { + cache_obj_t obj; + obj.obj_id = victim.id; + obj.create_time = victim.create_time; + record_eviction_age(cache, &obj, + CURR_TIME(cache, req) - victim.create_time); + } +#endif + + lhd->replaced(victim); + lhd->sizeMap.erase(victimItr); +} + +/** + * @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 + * + * @param cache + * @param obj_id + * @return true if the object is removed, false if the object is not in the + * cache + */ +static bool LHD_compute_remove(cache_t *cache, const obj_id_t obj_id) { + auto *params = static_cast(cache->eviction_params); + auto *lhd = static_cast(params->LHD_compute_cache); + repl::candidate_t id{DEFAULT_APP_ID, (int64_t)obj_id}; + + auto itr = lhd->sizeMap.find(id); + if (itr == lhd->sizeMap.end()) { + return false; + } + + cache->occupied_byte -= (itr->second + cache->obj_md_size); + cache->n_obj -= 1; + lhd->sizeMap.erase(itr); + auto idx = lhd->indices[id]; + lhd->indices.erase(id); + lhd->tags[idx] = lhd->tags.back(); + lhd->tags.pop_back(); + + if (idx < lhd->tags.size()) { + lhd->indices[lhd->tags[idx].id] = idx; + } + + return true; +} + +static int64_t LHD_compute_get_occupied_byte(const cache_t *cache) { + return cache->occupied_byte; +} + +static int64_t LHD_compute_get_n_obj(const cache_t *cache) { + return cache->n_obj; +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/libCacheSim/cache/eviction/LHD/lhd_compute.cpp b/libCacheSim/cache/eviction/LHD/lhd_compute.cpp new file mode 100644 index 00000000..0516711a --- /dev/null +++ b/libCacheSim/cache/eviction/LHD/lhd_compute.cpp @@ -0,0 +1,370 @@ +#include "lhd_compute.hpp" + +#include + +#include "constants.hpp" +#include "utils/include/mymath.h" + +namespace repl { + +LHDCompute::LHDCompute(int _associativity, int _admissions, cache_t* _cache) + : ASSOCIATIVITY(_associativity), + ADMISSIONS(_admissions), + cache(_cache), + recentlyAdmitted(ADMISSIONS, INVALID_CANDIDATE) { + nextReconfiguration = ACCS_PER_RECONFIGURATION; + explorerBudget = cache->cache_size * EXPLORER_BUDGET_FRACTION; + + for (uint32_t i = 0; i < NUM_CLASSES; i++) { + classes.push_back(Class()); + auto& cl = classes.back(); + cl.hits.resize(MAX_AGE, 0); + cl.evictions.resize(MAX_AGE, 0); + cl.hitDensities.resize(MAX_AGE, 0); + } + + // Initialize policy to ~GDSF by default. + // jason: why is this GDSF? and why the index of class is used in density + for (uint32_t c = 0; c < NUM_CLASSES; c++) { + for (age_t a = 0; a < MAX_AGE; a++) { + classes[c].hitDensities[a] = 1. * (c + 1) / (a + 1); + } + } +} + +candidate_t LHDCompute::rank(const request_t* req) { + uint64_t victim = -1; + rank_t victimRank = std::numeric_limits::max(); + + // Sample few candidates early in the trace so that we converge + // quickly to a reasonable policy. + // + // This is a hack to let us have shorter warmup so we can evaluate + // a longer fraction of the trace; doesn't belong in a real + // system. + uint32_t candidates = (numReconfigurations > 50) ? ASSOCIATIVITY : 8; + + for (uint32_t i = 0; i < candidates; i++) { + auto idx = next_rand() % tags.size(); + auto& tag = tags[idx]; + rank_t rank = getHitDensity(tag); + + if (rank < victimRank) { + victim = idx; + victimRank = rank; + } + } + + for (uint32_t i = 0; i < ADMISSIONS; i++) { + auto itr = indices.find(recentlyAdmitted[i]); + if (itr == indices.end()) { + continue; + } + + auto idx = itr->second; + auto& tag = tags[idx]; + assert(tag.id == recentlyAdmitted[i]); + rank_t rank = getHitDensity(tag); + + if (rank < victimRank) { + victim = idx; + victimRank = rank; + } + } + + assert(victim != (uint64_t)-1); + + ewmaVictimHitDensity = + EWMA_DECAY * ewmaVictimHitDensity + (1 - EWMA_DECAY) * victimRank; + + return tags[victim].id; +} + +void LHDCompute::update(candidate_t id, const request_t* req) { + auto itr = indices.find(id); + bool insert = (itr == indices.end()); + + Tag* tag; + if (insert) { + tags.push_back(Tag{}); + tag = &tags.back(); + indices[id] = tags.size() - 1; + + tag->lastLastHitAge = MAX_AGE; + tag->lastHitAge = 0; + tag->id = id; + } else { + tag = &tags[itr->second]; + assert(tag->id == id); + auto age = getAge(*tag); + auto& cl = getClass(*tag); + cl.hits[age] += 1; + + if (tag->explorer) { + explorerBudget += tag->size; + } + + tag->lastLastHitAge = tag->lastHitAge; + tag->lastHitAge = age; + } + + tag->timestamp = timestamp; + tag->app = DEFAULT_APP_ID % APP_CLASSES; + tag->size = 1; + tag->compute_intensity = req->features[0]; + + // with some probability, some candidates will never be evicted + // ... but limit how many resources we spend on doing this + bool explore = (next_rand() % EXPLORE_INVERSE_PROBABILITY) == 0; + if (explore && explorerBudget > 0 && numReconfigurations < 50) { + tag->explorer = true; + explorerBudget -= tag->size; + } else { + tag->explorer = false; + } + + // If this candidate looks like something that should be + // evicted, track it. + if (insert && !explore && getHitDensity(*tag) < ewmaVictimHitDensity) { + recentlyAdmitted[recentlyAdmittedHead++ % ADMISSIONS] = id; + } + + ++timestamp; + + if (--nextReconfiguration == 0) { + reconfigure(); + nextReconfiguration = ACCS_PER_RECONFIGURATION; + ++numReconfigurations; + } +} + +void LHDCompute::replaced(candidate_t id) { + auto itr = indices.find(id); + assert(itr != indices.end()); + auto index = itr->second; + + // Record stats before removing item + auto& tag = tags[index]; + assert(tag.id == id); + auto age = getAge(tag); + auto& cl = getClass(tag); + cl.evictions[age] += 1; + + if (tag.explorer) { + explorerBudget += tag.size; + } + + // Remove tag for replaced item and update index + indices.erase(itr); + tags[index] = tags.back(); + tags.pop_back(); + + if (index < tags.size()) { + indices[tags[index].id] = index; + } +} + +void LHDCompute::reconfigure() { + rank_t totalHits = 0; + rank_t totalEvictions = 0; + for (auto& cl : classes) { + updateClass(cl); + totalHits += cl.totalHits; + totalEvictions += cl.totalEvictions; + } + + adaptAgeCoarsening(); + + modelHitDensity(); + + // Just printfs ... + for (uint32_t c = 0; c < classes.size(); c++) { + auto& cl = classes[c]; + // printf("Class %d | hits %g, evictions %g, hitRate %g\n", + // c, + // cl.totalHits, cl.totalEvictions, + // cl.totalHits / (cl.totalHits + cl.totalEvictions)); + + dumpClassRanks(cl); + } + // printf("LHD | hits %g, evictions %g, hitRate %g | overflows %lu (%g) | + // cumulativeHitRate nan\n", + // totalHits, totalEvictions, + // totalHits / (totalHits + totalEvictions), + // (unsigned long) overflows, + // 1. * (double) overflows / ACCS_PER_RECONFIGURATION); + + overflows = 0; +} + +void LHDCompute::updateClass(Class& cl) { + cl.totalHits = 0; + cl.totalEvictions = 0; + + for (age_t age = 0; age < MAX_AGE; age++) { + cl.hits[age] *= EWMA_DECAY; + cl.evictions[age] *= EWMA_DECAY; + + cl.totalHits += cl.hits[age]; + cl.totalEvictions += cl.evictions[age]; + } +} + +void LHDCompute::modelHitDensity() { + for (uint32_t c = 0; c < classes.size(); c++) { + rank_t totalEvents = + classes[c].hits[MAX_AGE - 1] + classes[c].evictions[MAX_AGE - 1]; + rank_t totalHits = classes[c].hits[MAX_AGE - 1]; + rank_t lifetimeUnconditioned = totalEvents; + + // we use a small trick here to compute expectation in O(N) by + // accumulating all values at later ages in + // lifetimeUnconditioned. + + for (age_t a = MAX_AGE - 2; a < MAX_AGE; a--) { + totalHits += classes[c].hits[a]; + + totalEvents += classes[c].hits[a] + classes[c].evictions[a]; + + lifetimeUnconditioned += totalEvents; + + if (totalEvents > 1e-5) { + classes[c].hitDensities[a] = totalHits / lifetimeUnconditioned; + } else { + classes[c].hitDensities[a] = 0.; + } + } + } +} + +void LHDCompute::dumpClassRanks(Class& cl) { + if (!DUMP_RANKS) { + return; + } + + // float objectAvgSize = cl.sizeAccumulator / cl.totalHits; // + + // cl.totalEvictions); + float objectAvgSize = 1. * cache->occupied_byte / sizeMap.size(); + rank_t left; + + left = cl.totalHits + cl.totalEvictions; + std::cout << "Ranks for avg object (" << objectAvgSize << "): "; + for (age_t a = 0; a < MAX_AGE; a++) { + std::stringstream rankStr; + rank_t density = cl.hitDensities[a] / objectAvgSize; + rankStr << density << ", "; + std::cout << rankStr.str(); + + left -= cl.hits[a] + cl.evictions[a]; + if (rankStr.str() == "0, " && left < 1e-2) { + break; + } + } + std::cout << std::endl; + + left = cl.totalHits + cl.totalEvictions; + std::cout << "Hits: "; + for (uint32_t a = 0; a < MAX_AGE; a++) { + std::stringstream rankStr; + rankStr << cl.hits[a] << ", "; + std::cout << rankStr.str(); + + left -= cl.hits[a] + cl.evictions[a]; + if (rankStr.str() == "0, " && left < 1e-2) { + break; + } + } + std::cout << std::endl; + + left = cl.totalHits + cl.totalEvictions; + std::cout << "Evictions: "; + for (uint32_t a = 0; a < MAX_AGE; a++) { + std::stringstream rankStr; + rankStr << cl.evictions[a] << ", "; + std::cout << rankStr.str(); + + left -= cl.hits[a] + cl.evictions[a]; + if (rankStr.str() == "0, " && left < 1e-2) { + break; + } + } + std::cout << std::endl; +} + +// this happens very rarely! +// +// it is simple enough to set the age coarsening if you know roughly +// how big your objects are. to make LHD run on different traces +// without needing to configure this, we set the age coarsening +// automatically near the beginning of the trace. +void LHDCompute::adaptAgeCoarsening() { + ewmaNumObjects *= EWMA_DECAY; + ewmaNumObjectsMass *= EWMA_DECAY; + + ewmaNumObjects += sizeMap.size(); + ewmaNumObjectsMass += 1.; + + rank_t numObjects = ewmaNumObjects / ewmaNumObjectsMass; + + rank_t optimalAgeCoarsening = + 1. * numObjects / (AGE_COARSENING_ERROR_TOLERANCE * MAX_AGE); + + // Simplify. Just do this once shortly after the trace starts and + // again after 25 iterations. It only matters that we are within + // the right order of magnitude to avoid tons of overflows. + if (numReconfigurations == 5 || numReconfigurations == 25) { + uint32_t optimalAgeCoarseningLog2 = 1; + + while ((1 << optimalAgeCoarseningLog2) < optimalAgeCoarsening) { + optimalAgeCoarseningLog2 += 1; + } + + int32_t delta = optimalAgeCoarseningLog2 - ageCoarseningShift; + ageCoarseningShift = optimalAgeCoarseningLog2; + + // increase weight to delay another shift for a while + ewmaNumObjects *= 8; + ewmaNumObjectsMass *= 8; + + // compress or stretch distributions to approximate new scaling + // regime + if (delta < 0) { + // stretch + for (auto& cl : classes) { + for (age_t a = MAX_AGE >> (-delta); a < MAX_AGE - 1; a++) { + cl.hits[MAX_AGE - 1] += cl.hits[a]; + cl.evictions[MAX_AGE - 1] += cl.evictions[a]; + } + for (age_t a = MAX_AGE - 2; a < MAX_AGE; a--) { + cl.hits[a] = cl.hits[a >> (-delta)] / (1 << (-delta)); + cl.evictions[a] = cl.evictions[a >> (-delta)] / (1 << (-delta)); + } + } + } else if (delta > 0) { + // compress + for (auto& cl : classes) { + for (age_t a = 0; a < MAX_AGE >> delta; a++) { + cl.hits[a] = cl.hits[a << delta]; + cl.evictions[a] = cl.evictions[a << delta]; + for (int i = 1; i < (1 << delta); i++) { + cl.hits[a] += cl.hits[(a << delta) + i]; + cl.evictions[a] += cl.evictions[(a << delta) + i]; + } + } + for (age_t a = (MAX_AGE >> delta); a < MAX_AGE - 1; a++) { + cl.hits[a] = 0; + cl.evictions[a] = 0; + } + } + } + } + + // printf("LHD at %lu | ageCoarseningShift now %lu | num objects %g | + // optimal age coarsening %g | current age coarsening %g\n", + // (unsigned long) timestamp, (unsigned long) ageCoarseningShift, + // numObjects, + // optimalAgeCoarsening, + // 1. * (1 << ageCoarseningShift)); +} + +} // namespace repl diff --git a/libCacheSim/cache/eviction/LHD/lhd_compute.hpp b/libCacheSim/cache/eviction/LHD/lhd_compute.hpp new file mode 100644 index 00000000..175d0187 --- /dev/null +++ b/libCacheSim/cache/eviction/LHD/lhd_compute.hpp @@ -0,0 +1,198 @@ +#pragma once + +#include + +#include +#include + +#include "libCacheSim/cache.h" +#include "repl.hpp" + +namespace repl { + +class LHDCompute { + public: + // TYPES /////////////////////////////// + typedef uint64_t timestamp_t; + typedef uint64_t age_t; + typedef float rank_t; + + // info we track about each object + struct Tag { + double compute_intensity; + age_t timestamp; + age_t lastHitAge; + age_t lastLastHitAge; + uint32_t app; + + candidate_t id; + rank_t size; // stored redundantly with cache + bool explorer; + }; + + // info we track about each class of objects + struct Class { + std::vector hits; + std::vector evictions; + rank_t totalHits = 0; + rank_t totalEvictions = 0; + + std::vector hitDensities; + }; + + LHDCompute(int _associativity, int _admissions, cache_t *cache); + ~LHDCompute() {} + + // called whenever and object is referenced + void update(candidate_t id, const request_t *req); + + // called when an object is evicted + void replaced(candidate_t id); + + // called to find a victim upon a cache miss + candidate_t rank(const request_t *req); + + void dumpStats(LHDCache::Cache *cache_params) {} + + std::unordered_map sizeMap; + // object metadata; indices maps object id -> metadata + std::vector tags; + std::vector classes; + std::unordered_map indices; + + private: + // CONSTANTS /////////////////////////// + + // how to sample candidates; can significantly impact hit + // ratio. want a value at least 32; diminishing returns after + // that. + const uint32_t ASSOCIATIVITY = 32; + + // since our cache simulator doesn't bypass objects, we always + // consider the last ADMISSIONS objects as eviction candidates + // (this is important to avoid very large objects polluting the + // cache.) alternatively, you could do bypassing and randomly + // admit objects as "explorers" (see below). + const uint32_t ADMISSIONS = 8; + + // escape local minima by having some small fraction of cache + // space allocated to objects that aren't evicted. 1% seems to be + // a good value that has limited impact on hit ratio while quickly + // discovering the shape of the access pattern. + static constexpr rank_t EXPLORER_BUDGET_FRACTION = 0.01; + static constexpr uint32_t EXPLORE_INVERSE_PROBABILITY = 32; + + // these parameters determine how aggressively to classify objects. + // diminishing returns after a few classes; 16 is safe. + static constexpr uint32_t HIT_AGE_CLASSES = 16; + static constexpr uint32_t APP_CLASSES = 16; + static constexpr uint32_t NUM_CLASSES = HIT_AGE_CLASSES * APP_CLASSES; + + // these parameters are tuned for simulation performance, and hit + // ratio is insensitive to them at reasonable values (like these) + static constexpr rank_t AGE_COARSENING_ERROR_TOLERANCE = 0.01; + static constexpr age_t MAX_AGE = 20000; + static constexpr timestamp_t ACCS_PER_RECONFIGURATION = (1 << 20); + static constexpr rank_t EWMA_DECAY = 0.9; + + // verbose debugging output? + static constexpr bool DUMP_RANKS = false; + + // FIELDS ////////////////////////////// + cache_t *cache; + // repl::CandidateMap historyAccess; + + // time is measured in # of requests + timestamp_t timestamp = 0; + + timestamp_t nextReconfiguration = 0; + int numReconfigurations = 0; + + // how much to shift down age values; initial value doesn't really + // matter, but must be positive. tuned in adaptAgeCoarsening() at + // the beginning of the trace using ewmaNumObjects. + timestamp_t ageCoarseningShift = 10; + rank_t ewmaNumObjects = 0; + rank_t ewmaNumObjectsMass = 0.; + + // how many objects had age > max age (this should almost never + // happen -- if you observe non-negligible overflows, something has + // gone wrong with the age coarsening!!!) + uint64_t overflows = 0; + + // misc::Rand rand; + + // see ADMISSIONS above + std::vector recentlyAdmitted; + int recentlyAdmittedHead = 0; + rank_t ewmaVictimHitDensity = 0; + + int64_t explorerBudget = 0; + + // METHODS ///////////////////////////// + + // returns something like log(maxAge - age) + inline uint32_t hitAgeClass(age_t age) const { + if (age == 0) { + return HIT_AGE_CLASSES - 1; + } + uint32_t log = 0; + while (age < MAX_AGE && log < HIT_AGE_CLASSES - 1) { + age <<= 1; + log += 1; + } + return log; + } + + inline uint32_t getClassId(const Tag &tag) const { + uint32_t hitAgeId = hitAgeClass(tag.lastHitAge + tag.lastLastHitAge); + // uint32_t hitAgeId = hitAgeClass(tag.lastHitAge); + return tag.app * HIT_AGE_CLASSES + hitAgeId; + } + + inline uint32_t getClassIdBySize(const Tag &tag) const { + uint32_t hitSizeId = 0; + uint64_t size = (uint64_t)tag.size; + return tag.app * HIT_AGE_CLASSES + ((uint64_t)log(size)) % HIT_AGE_CLASSES; + } + + inline uint32_t getClassIdBySizeAndAge(const Tag &tag) const { + if (tag.lastHitAge == 0) return getClassIdBySize(tag); + + uint32_t hitSizeId = 0; + uint64_t size = (uint64_t)tag.size; + return tag.app * HIT_AGE_CLASSES + + ((uint64_t)log(size) + (uint64_t)log(tag.lastHitAge)) % + HIT_AGE_CLASSES; + } + + inline Class &getClass(const Tag &tag) { + // return classes[getClassIdBySizeAndAge(tag)]; + // return classes[getClassIdBySize(tag)]; + return classes[getClassId(tag)]; + } + + inline age_t getAge(Tag tag) { + timestamp_t age = + (timestamp - (timestamp_t)tag.timestamp) >> ageCoarseningShift; + + if (age >= MAX_AGE) { + ++overflows; + return MAX_AGE - 1; + } else { + return (age_t)age; + } + } + + inline rank_t getHitDensity(const Tag& tag) { + return getClass(tag).hitDensities[getAge(tag)] * tag.compute_intensity; + } + + void reconfigure(); + void adaptAgeCoarsening(); + void updateClass(Class &cl); + void modelHitDensity(); + void dumpClassRanks(Class &cl); +}; + +} // namespace repl diff --git a/libCacheSim/cache/eviction/S3FIFOCompute.c b/libCacheSim/cache/eviction/S3FIFOCompute.c new file mode 100644 index 00000000..75ddcdea --- /dev/null +++ b/libCacheSim/cache/eviction/S3FIFOCompute.c @@ -0,0 +1,613 @@ +// +// compute-intensity-aware S3-FIFO +// admit high compute intensity object with higher priority +// +// S3FIFOCompute.c +// libCacheSim +// +// Created by Code Assistant on 2/6/26. +// Copyright © 2026 Code Assistant. All rights reserved. +// + +#include "../../dataStructure/hashtable/hashtable.h" +#include "../../include/libCacheSim/evictionAlgo.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// #define PROB_ADMISSION +// #define USE_FILTER + +typedef struct { + cache_t *small; + cache_t *ghost; + cache_t *main; + + int64_t n_byte_admit_to_small; + int64_t n_byte_admit_to_main; + int64_t n_byte_move_to_main; + int64_t n_byte_reinsert_to_main; + + int move_to_main_threshold; + double small_size_ratio; + double ghost_size_ratio; + + bool has_evicted; + request_t *req_local; +} S3FIFOCompute_params_t; + +static const char *DEFAULT_CACHE_PARAMS = "small-size-ratio=0.10,ghost-size-ratio=0.90,move-to-main-threshold=1"; + +// *********************************************************************** +// **** **** +// **** function declarations **** +// **** **** +// *********************************************************************** +static void S3FIFOCompute_free(cache_t *cache); +static bool S3FIFOCompute_get(cache_t *cache, const request_t *req); + +static cache_obj_t *S3FIFOCompute_find(cache_t *cache, const request_t *req, const bool update_cache); +static cache_obj_t *S3FIFOCompute_insert(cache_t *cache, const request_t *req); +static cache_obj_t *S3FIFOCompute_to_evict(cache_t *cache, const request_t *req); +static void S3FIFOCompute_evict(cache_t *cache, const request_t *req); +static bool S3FIFOCompute_remove(cache_t *cache, const obj_id_t obj_id); +static inline int64_t S3FIFOCompute_get_occupied_byte(const cache_t *cache); +static inline int64_t S3FIFOCompute_get_n_obj(const cache_t *cache); +static inline bool S3FIFOCompute_can_insert(cache_t *cache, const request_t *req); +static void S3FIFOCompute_parse_params(cache_t *cache, const char *cache_specific_params); + +static void S3FIFOCompute_evict_fifo(cache_t *cache, const request_t *req); +static void S3FIFOCompute_evict_main(cache_t *cache, const request_t *req); + +// *********************************************************************** +// **** **** +// **** end user facing functions **** +// **** **** +// *********************************************************************** + +cache_t *S3FIFOCompute_init(const common_cache_params_t ccache_params, const char *cache_specific_params) { + cache_t *cache = cache_struct_init("S3FIFOCompute", ccache_params, cache_specific_params); + cache->cache_init = S3FIFOCompute_init; + cache->cache_free = S3FIFOCompute_free; + cache->get = S3FIFOCompute_get; + cache->find = S3FIFOCompute_find; + cache->insert = S3FIFOCompute_insert; + cache->evict = S3FIFOCompute_evict; + cache->remove = S3FIFOCompute_remove; + cache->to_evict = S3FIFOCompute_to_evict; + cache->get_n_obj = S3FIFOCompute_get_n_obj; + cache->get_occupied_byte = S3FIFOCompute_get_occupied_byte; + cache->can_insert = S3FIFOCompute_can_insert; + + cache->obj_md_size = 0; + + cache->eviction_params = malloc(sizeof(S3FIFOCompute_params_t)); + memset(cache->eviction_params, 0, sizeof(S3FIFOCompute_params_t)); + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + params->req_local = new_request(); + + S3FIFOCompute_parse_params(cache, DEFAULT_CACHE_PARAMS); + if (cache_specific_params != NULL) { + S3FIFOCompute_parse_params(cache, cache_specific_params); + } + + int64_t fifo_cache_size = (int64_t)ccache_params.cache_size * params->small_size_ratio; + int64_t main_size = ccache_params.cache_size - fifo_cache_size; + int64_t ghost_cache_size = (int64_t)(ccache_params.cache_size * params->ghost_size_ratio); + + common_cache_params_t ccache_params_local = ccache_params; + ccache_params_local.cache_size = fifo_cache_size; + params->small = FIFO_init(ccache_params_local, NULL); + + params->has_evicted = false; + + if (ghost_cache_size > 0) { + ccache_params_local.cache_size = ghost_cache_size; + params->ghost = FIFO_init(ccache_params_local, NULL); + snprintf(params->ghost->cache_name, CACHE_NAME_ARRAY_LEN, "FIFO-ghost"); + } else { + params->ghost = NULL; + } + + ccache_params_local.cache_size = main_size; + params->main = FIFO_init(ccache_params_local, NULL); + + snprintf(cache->cache_name, CACHE_NAME_ARRAY_LEN, "S3FIFOCompute-%.4lf-%d", params->small_size_ratio, + params->move_to_main_threshold); + + return cache; +} + +/** + * free resources used by this cache + * + * @param cache + */ +static void S3FIFOCompute_free(cache_t *cache) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + free_request(params->req_local); + params->small->cache_free(params->small); + if (params->ghost != NULL) { + params->ghost->cache_free(params->ghost); + } + params->main->cache_free(params->main); + free(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 S3FIFOCompute_get(cache_t *cache, const request_t *req) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + DEBUG_ASSERT(params->small->get_occupied_byte(params->small) + params->main->get_occupied_byte(params->main) <= + cache->cache_size); + + cache->n_req += 1; + + // if (cache->n_req == 1397750 || cache->n_req == 24733818 || cache->n_req == 85580270) { + // printf("cache size %ldMB, %ld MREQ, SSD write %.4lf + %.4lf + %.4lf=%.4lfGB\n", cache->cache_size / MiB, + // cache->n_req / 1000000, + // params->n_byte_admit_to_main / 1e9, params->n_byte_move_to_main / 1e9, params->n_byte_reinsert_to_main / + // 1e9, params->n_byte_admit_to_main / 1e9+params->n_byte_move_to_main / 1e9+params->n_byte_reinsert_to_main + // / 1e9); + // } + + cache_obj_t *obj = cache->find(cache, req, true); + bool cache_hit = (obj != NULL); + + if (!cache_hit) { + if (!cache->can_insert(cache, req)) { + obj = params->ghost->find(params->ghost, req, false); + if (obj == NULL) { + obj = params->ghost->insert(params->ghost, req); + obj->S3FIFO.freq = 1; + } else { + obj->S3FIFO.freq += 1; + } + + } else { + while (cache->get_occupied_byte(cache) + req->obj_size + cache->obj_md_size > cache->cache_size) { + cache->evict(cache, req); + } + cache->insert(cache, req); + } + } + return cache_hit; +} + +// *********************************************************************** +// **** **** +// **** developer facing APIs (used by cache developer) **** +// **** **** +// *********************************************************************** +static void cal_mean_obj_size(cache_t *cache, double req_obj_size, double *mean_obj_size_in_small, + double *mean_obj_size_in_main, double *mean_obj_size) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + cache_t *small_q = params->small; + cache_t *main_q = params->main; + + double small_q_n_obj = MAX(small_q->get_n_obj(small_q), 1e-8); + double small_q_byte = small_q->get_occupied_byte(small_q); + double main_q_n_obj = MAX(main_q->get_n_obj(main_q), 1e-8); + double main_q_byte = main_q->get_occupied_byte(main_q); + double cache_n_obj = MAX(cache->get_n_obj(cache), 1e-8); + double cache_byte = cache->get_occupied_byte(cache); + + // For compute intensity variant, we use compute intensity from request + // but calculate mean object size as usual for backward compatibility + if (mean_obj_size_in_small != NULL) { + *mean_obj_size_in_small = small_q_byte / small_q_n_obj; + } + if (mean_obj_size_in_main != NULL) { + *mean_obj_size_in_main = main_q_byte / main_q_n_obj; + } + if (mean_obj_size != NULL) { + *mean_obj_size = cache_byte / cache_n_obj; + } +} + +/** + * @brief find an object in the cache + * + * @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 *S3FIFOCompute_find(cache_t *cache, const request_t *req, const bool update_cache) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + + // if update cache is false, we only check the fifo and main caches + if (!update_cache) { + cache_obj_t *obj = params->small->find(params->small, req, false); + if (obj != NULL) { + return obj; + } + obj = params->main->find(params->main, req, false); + return obj; + } + + /* update cache is true from now */ + cache_obj_t *obj = params->small->find(params->small, req, true); + if (obj != NULL) { +#ifdef USE_FILTER + if (params->n_byte_admit_to_small - obj->S3FIFO.insertion_time > params->small->cache_size / 2) { + obj->S3FIFO.freq += 1; + // } else { + // params->small->get_occupied_byte(params->small)/10000000); + } +#else + obj->S3FIFO.freq += 1; +#endif + return obj; + } + + obj = params->main->find(params->main, req, true); + if (obj != NULL) { + obj->S3FIFO.freq += 1; + } + + return obj; +} + +/** + * @brief insert an object into the cache, + * update the hash table and cache metadata + * this function 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 *S3FIFOCompute_insert(cache_t *cache, const request_t *req) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + cache_obj_t *obj = NULL; + + cache_t *main_q = params->main; + cache_t *ghost_q = params->ghost; + + double mean_obj_size_in_small, mean_obj_size_in_main, mean_obj_size; + cal_mean_obj_size(cache, req->obj_size, &mean_obj_size_in_small, &mean_obj_size_in_main, &mean_obj_size); + + cache_obj_t *ghost_obj = ghost_q->find(ghost_q, req, false); + assert(ghost_obj == NULL || ghost_obj->S3FIFO.freq > 0); + + if (ghost_obj != NULL) { + // we need to compare with the small queue, because the object has not had chance to accumulate enough hits in the + // main queue + // MODIFIED: Invert the size-based logic for compute intensity + // Original: ratio = req_obj_size / mean_obj_size_in_small + // Higher obj_size -> higher ratio -> harder to promote + // Modified: ratio = mean_obj_size_in_small / req_compute_intensity + // Higher compute_intensity -> lower ratio -> easier to promote + double compute_intensity = req->features[0]; + if (compute_intensity <= 0) compute_intensity = 1.0; // Avoid division by zero + double ratio = mean_obj_size_in_small / compute_intensity; + + if ((ghost_obj->S3FIFO.freq) / ratio >= params->move_to_main_threshold) { + // insert to main + params->n_byte_admit_to_main += req->obj_size; + obj = main_q->insert(main_q, req); + obj->S3FIFO.freq = 1; + } else { + // insert to small FIFO + params->n_byte_admit_to_small += req->obj_size; + obj = params->small->insert(params->small, req); + obj->S3FIFO.insertion_time = params->n_byte_admit_to_small; + // only keep the frequency when inserting into the small queue + // the ghost frequency has not been updated + obj->S3FIFO.freq = ghost_obj->S3FIFO.freq + 1; + } + ghost_q->remove(ghost_q, req->obj_id); + } else { + int64_t small_q_byte = params->small->get_occupied_byte(params->small); + int64_t small_q_cache_size = params->small->cache_size; + + // if (!params->has_evicted && small_q_byte >= small_q_cache_size) { + // params->n_byte_admit_to_main += req->obj_size; + // obj = main_q->insert(main_q, req); + // } else { + // params->n_byte_admit_to_small += req->obj_size; + // obj = params->small->insert(params->small, req); + // obj->S3FIFO.insertion_time = params->n_byte_admit_to_small; + // } + + if (!params->has_evicted) { + if (main_q->get_occupied_byte(main_q) + req->obj_size + cache->obj_md_size <= main_q->cache_size) { + params->n_byte_admit_to_main += req->obj_size; + obj = main_q->insert(main_q, req); + } else if (small_q_byte + req->obj_size + cache->obj_md_size >= small_q_cache_size) { + ERROR("both small and main queue are full, but we are not evicting\n"); + } else { + params->n_byte_admit_to_small += req->obj_size; + obj = params->small->insert(params->small, req); + obj->S3FIFO.insertion_time = params->n_byte_admit_to_small; + } + } else { + params->n_byte_admit_to_small += req->obj_size; + obj = params->small->insert(params->small, req); + obj->S3FIFO.insertion_time = params->n_byte_admit_to_small; + } + + obj->S3FIFO.freq = 1; + } + + return obj; +} + +/** + * @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 + * + * @param cache the cache + * @return the object to be evicted + */ +static cache_obj_t *S3FIFOCompute_to_evict(cache_t *cache, const request_t *req) { + assert(false); + return NULL; +} + +// evict from FIFO +static void S3FIFOCompute_evict_fifo(cache_t *cache, const request_t *req) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + cache_t *small_q = params->small; + cache_t *main_q = params->main; + + cache_obj_t *obj_to_evict = small_q->to_evict(small_q, req); + DEBUG_ASSERT(obj_to_evict != NULL); + // need to copy the object before it is evicted + copy_cache_obj_to_request(params->req_local, obj_to_evict); + + double cache_n_obj = MAX(cache->get_n_obj(cache), 1e-8); + double cache_byte = cache->get_occupied_byte(cache); + + double mean_obj_size = cache_byte / cache_n_obj; + + int64_t obj_size = obj_to_evict->obj_size; + // MODIFIED: Apply compute intensity logic + double compute_intensity = req->features[0]; + if (compute_intensity <= 0) compute_intensity = 1.0; // Avoid division by zero + double ratio = mean_obj_size / compute_intensity; + + if ((obj_to_evict->S3FIFO.freq) / ratio >= params->move_to_main_threshold) { + params->n_byte_move_to_main += obj_to_evict->obj_size; + cache_obj_t *new_obj = main_q->insert(main_q, params->req_local); + new_obj->S3FIFO.freq = 1; + } else { + // insert to ghost + if (params->ghost != NULL) { + params->ghost->get(params->ghost, params->req_local); + cache_obj_t *ghost_obj = params->ghost->find(params->ghost, params->req_local, false); + ghost_obj->S3FIFO.freq = obj_to_evict->S3FIFO.freq; + } + } + + small_q->evict(small_q, params->req_local); +} + +// evict from main cache +static void S3FIFOCompute_evict_main(cache_t *cache, const request_t *req) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + cache_t *small_q = params->small; + cache_t *main_q = params->main; + cache_t *ghost_q = params->ghost; + + double cache_n_obj = MAX(cache->get_n_obj(cache), 1e-8); + double cache_byte = cache->get_occupied_byte(cache); + + cache_obj_t *obj_to_evict = main_q->to_evict(main_q, req); + int freq = obj_to_evict->S3FIFO.freq; + copy_cache_obj_to_request(params->req_local, obj_to_evict); + double mean_obj_size = cache_byte / cache_n_obj; + + // MODIFIED: Apply compute intensity logic + double compute_intensity = req->features[0]; + if (compute_intensity <= 0) compute_intensity = 1.0; // Avoid division by zero + double ratio = mean_obj_size / compute_intensity; + + bool removed = main_q->remove(main_q, obj_to_evict->obj_id); + DEBUG_ASSERT(removed); + + if ((double)(freq) / ratio >= params->move_to_main_threshold) { + cache_obj_t *new_obj = main_q->insert(main_q, params->req_local); + // clock with 2-bit counter, 4 is better than 3 + new_obj->S3FIFO.freq = MIN(freq, 4) - 1; + params->n_byte_reinsert_to_main += new_obj->obj_size; + } +} + +/** + * @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 + * + * @param cache + * @param req not used + * @param evicted_obj if not NULL, return the evicted object to caller + */ +static void S3FIFOCompute_evict(cache_t *cache, const request_t *req) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + params->has_evicted = true; + + cache_t *small_q = params->small; + cache_t *main_q = params->main; + cache_t *ghost_q = params->ghost; + + do { + // printf("req %ld size %ld cache size %ld/%ld + %ld/%ld = %ld, %ld left\n", req->obj_size / 1000, cache->n_req, + // params->small->get_occupied_byte(params->small) / 1000, params->small->cache_size / 1000, + // params->main->get_occupied_byte(params->main) / 1000, params->main->cache_size / 1000, + // (cache->get_occupied_byte(cache) + req->obj_size + cache->obj_md_size) / 1000, + // cache->cache_size / 1000 - cache->get_occupied_byte(cache) / 1000); + + if (main_q->get_occupied_byte(main_q) > main_q->cache_size || small_q->get_occupied_byte(small_q) == 0) { + S3FIFOCompute_evict_main(cache, req); + // main_q->evict(main_q, req); + } else { + S3FIFOCompute_evict_fifo(cache, req); + } + } while (cache->get_occupied_byte(cache) + req->obj_size + cache->obj_md_size > cache->cache_size); +} + +/** + * @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 + * + * @param cache + * @param obj_id + * @return true if the object is removed, false if the object is not in the + * cache + */ +static bool S3FIFOCompute_remove(cache_t *cache, const obj_id_t obj_id) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + bool removed = false; + removed = removed || params->small->remove(params->small, obj_id); + removed = removed || (params->ghost && params->ghost->remove(params->ghost, obj_id)); + removed = removed || params->main->remove(params->main, obj_id); + + return removed; +} + +static inline int64_t S3FIFOCompute_get_occupied_byte(const cache_t *cache) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + return params->small->get_occupied_byte(params->small) + params->main->get_occupied_byte(params->main); +} + +static inline int64_t S3FIFOCompute_get_n_obj(const cache_t *cache) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + return params->small->get_n_obj(params->small) + params->main->get_n_obj(params->main); +} + +static bool can_insert_to_small(const cache_t *cache, const request_t *req) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + +#ifdef PROB_ADMISSION + double r = (double)(req->obj_size) / params->small->cache_size; + if (next_rand_double() < r) { + return false; + } +#else + if (req->obj_size >= params->small->cache_size) { + return false; + } +#endif + return true; +} + +static bool S3FIFOCompute_can_insert(cache_t *cache, const request_t *req) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)cache->eviction_params; + + cache_t *small_q = params->small; + cache_t *main_q = params->main; + cache_t *ghost_q = params->ghost; + + double mean_obj_size_in_small, mean_obj_size_in_main, mean_obj_size; + cal_mean_obj_size(cache, req->obj_size, &mean_obj_size_in_small, &mean_obj_size_in_main, &mean_obj_size); + + // MODIFIED: Apply compute intensity logic for admission + double compute_intensity = req->features[0]; + if (compute_intensity <= 0) compute_intensity = 1.0; // Avoid division by zero + double ratio = mean_obj_size_in_small / compute_intensity; + + cache_obj_t *ghost_obj = ghost_q->find(ghost_q, req, false); + + if (ghost_obj != NULL) { + if ((ghost_obj->S3FIFO.freq) / ratio >= params->move_to_main_threshold) { + if (req->obj_size >= params->main->cache_size) { + return false; + } + } else { + if (!can_insert_to_small(cache, req)) { + return false; + } + } + } else { + if (!can_insert_to_small(cache, req)) { + return false; + } + } + + return cache_can_insert_default(cache, req); +} + +// *********************************************************************** +// **** **** +// **** parameter set up functions **** +// **** **** +// *********************************************************************** +static const char *S3FIFOCompute_current_params(S3FIFOCompute_params_t *params) { + static __thread char params_str[128]; + snprintf(params_str, 128, "fifo-size-ratio=%.4lf,ghost-size-ratio=%.4lf,move-to-main-threshold=%d\n", + params->small_size_ratio, params->ghost_size_ratio, params->move_to_main_threshold); + return params_str; +} + +static void S3FIFOCompute_parse_params(cache_t *cache, const char *cache_specific_params) { + S3FIFOCompute_params_t *params = (S3FIFOCompute_params_t *)(cache->eviction_params); + + char *params_str = strdup(cache_specific_params); + char *old_params_str = params_str; + char *end; + + while (params_str != NULL && params_str[0] != '\0') { + /* different parameters are separated by comma, + * key and value are separated by = */ + char *key = strsep((char **)¶ms_str, "="); + char *value = strsep((char **)¶ms_str, ","); + + // skip the white space + while (params_str != NULL && *params_str == ' ') { + params_str++; + } + + if (strcasecmp(key, "fifo-size-ratio") == 0 || strcasecmp(key, "small-size-ratio") == 0 || + strcasecmp(key, "small-queue-size") == 0) { + params->small_size_ratio = strtod(value, NULL); + } else if (strcasecmp(key, "ghost-size-ratio") == 0) { + params->ghost_size_ratio = strtod(value, NULL); + } else if (strcasecmp(key, "move-to-main-threshold") == 0) { + params->move_to_main_threshold = atoi(value); + } else if (strcasecmp(key, "print") == 0) { + printf("parameters: %s\n", S3FIFOCompute_current_params(params)); + exit(0); + } else { + ERROR("%s does not have parameter %s\n", cache->cache_name, key); + exit(1); + } + } + + free(old_params_str); +} + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/libCacheSim/cache/eviction/S3FIFOSize.c b/libCacheSim/cache/eviction/S3FIFOSize.c new file mode 100644 index 00000000..d1510372 --- /dev/null +++ b/libCacheSim/cache/eviction/S3FIFOSize.c @@ -0,0 +1,596 @@ +// +// size-aware S3-FIFO +// admit large object with probability +// +// S3FIFOSize.c +// libCacheSim +// +// Created by Juncheng on 12/4/22. +// Copyright © 2018 Juncheng. All rights reserved. +// + +#include "../../dataStructure/hashtable/hashtable.h" +#include "../../include/libCacheSim/evictionAlgo.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// #define PROB_ADMISSION +// #define USE_FILTER + +typedef struct { + cache_t *small; + cache_t *ghost; + cache_t *main; + + int64_t n_byte_admit_to_small; + int64_t n_byte_admit_to_main; + int64_t n_byte_move_to_main; + int64_t n_byte_reinsert_to_main; + + int move_to_main_threshold; + double small_size_ratio; + double ghost_size_ratio; + + bool has_evicted; + request_t *req_local; +} S3FIFOSize_params_t; + +static const char *DEFAULT_CACHE_PARAMS = "small-size-ratio=0.10,ghost-size-ratio=0.90,move-to-main-threshold=1"; + +// *********************************************************************** +// **** **** +// **** function declarations **** +// **** **** +// *********************************************************************** +cache_t *S3FIFOSize_init(const common_cache_params_t ccache_params, const char *cache_specific_params); +static void S3FIFOSize_free(cache_t *cache); +static bool S3FIFOSize_get(cache_t *cache, const request_t *req); + +static cache_obj_t *S3FIFOSize_find(cache_t *cache, const request_t *req, const bool update_cache); +static cache_obj_t *S3FIFOSize_insert(cache_t *cache, const request_t *req); +static cache_obj_t *S3FIFOSize_to_evict(cache_t *cache, const request_t *req); +static void S3FIFOSize_evict(cache_t *cache, const request_t *req); +static bool S3FIFOSize_remove(cache_t *cache, const obj_id_t obj_id); +static inline int64_t S3FIFOSize_get_occupied_byte(const cache_t *cache); +static inline int64_t S3FIFOSize_get_n_obj(const cache_t *cache); +static inline bool S3FIFOSize_can_insert(cache_t *cache, const request_t *req); +static void S3FIFOSize_parse_params(cache_t *cache, const char *cache_specific_params); + +static void S3FIFOSize_evict_fifo(cache_t *cache, const request_t *req); +static void S3FIFOSize_evict_main(cache_t *cache, const request_t *req); + +// *********************************************************************** +// **** **** +// **** end user facing functions **** +// **** **** +// *********************************************************************** + +cache_t *S3FIFOSize_init(const common_cache_params_t ccache_params, const char *cache_specific_params) { + cache_t *cache = cache_struct_init("S3FIFOSize", ccache_params, cache_specific_params); + cache->cache_init = S3FIFOSize_init; + cache->cache_free = S3FIFOSize_free; + cache->get = S3FIFOSize_get; + cache->find = S3FIFOSize_find; + cache->insert = S3FIFOSize_insert; + cache->evict = S3FIFOSize_evict; + cache->remove = S3FIFOSize_remove; + cache->to_evict = S3FIFOSize_to_evict; + cache->get_n_obj = S3FIFOSize_get_n_obj; + cache->get_occupied_byte = S3FIFOSize_get_occupied_byte; + cache->can_insert = S3FIFOSize_can_insert; + + cache->obj_md_size = 0; + + cache->eviction_params = malloc(sizeof(S3FIFOSize_params_t)); + memset(cache->eviction_params, 0, sizeof(S3FIFOSize_params_t)); + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + params->req_local = new_request(); + + S3FIFOSize_parse_params(cache, DEFAULT_CACHE_PARAMS); + if (cache_specific_params != NULL) { + S3FIFOSize_parse_params(cache, cache_specific_params); + } + + int64_t fifo_cache_size = (int64_t)ccache_params.cache_size * params->small_size_ratio; + int64_t main_size = ccache_params.cache_size - fifo_cache_size; + int64_t ghost_cache_size = (int64_t)(ccache_params.cache_size * params->ghost_size_ratio); + + common_cache_params_t ccache_params_local = ccache_params; + ccache_params_local.cache_size = fifo_cache_size; + params->small = FIFO_init(ccache_params_local, NULL); + + params->has_evicted = false; + + if (ghost_cache_size > 0) { + ccache_params_local.cache_size = ghost_cache_size; + params->ghost = FIFO_init(ccache_params_local, NULL); + snprintf(params->ghost->cache_name, CACHE_NAME_ARRAY_LEN, "FIFO-ghost"); + } else { + params->ghost = NULL; + } + + ccache_params_local.cache_size = main_size; + params->main = FIFO_init(ccache_params_local, NULL); + + snprintf(cache->cache_name, CACHE_NAME_ARRAY_LEN, "S3FIFOSize-%.4lf-%d", params->small_size_ratio, + params->move_to_main_threshold); + + return cache; +} + +/** + * free resources used by this cache + * + * @param cache + */ +static void S3FIFOSize_free(cache_t *cache) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + free_request(params->req_local); + params->small->cache_free(params->small); + if (params->ghost != NULL) { + params->ghost->cache_free(params->ghost); + } + params->main->cache_free(params->main); + free(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 S3FIFOSize_get(cache_t *cache, const request_t *req) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + DEBUG_ASSERT(params->small->get_occupied_byte(params->small) + params->main->get_occupied_byte(params->main) <= + cache->cache_size); + + cache->n_req += 1; + + // if (cache->n_req == 1397750 || cache->n_req == 24733818 || cache->n_req == 85580270) { + // printf("cache size %ldMB, %ld MREQ, SSD write %.4lf + %.4lf + %.4lf=%.4lfGB\n", cache->cache_size / MiB, + // cache->n_req / 1000000, + // params->n_byte_admit_to_main / 1e9, params->n_byte_move_to_main / 1e9, params->n_byte_reinsert_to_main / + // 1e9, params->n_byte_admit_to_main / 1e9+params->n_byte_move_to_main / 1e9+params->n_byte_reinsert_to_main + // / 1e9); + // } + + cache_obj_t *obj = cache->find(cache, req, true); + bool cache_hit = (obj != NULL); + + if (!cache_hit) { + if (!cache->can_insert(cache, req)) { + obj = params->ghost->find(params->ghost, req, false); + if (obj == NULL) { + obj = params->ghost->insert(params->ghost, req); + obj->S3FIFO.freq = 1; + } else { + obj->S3FIFO.freq += 1; + } + + } else { + while (cache->get_occupied_byte(cache) + req->obj_size + cache->obj_md_size > cache->cache_size) { + cache->evict(cache, req); + } + cache->insert(cache, req); + } + } + return cache_hit; +} + +// *********************************************************************** +// **** **** +// **** developer facing APIs (used by cache developer) **** +// **** **** +// *********************************************************************** +static void cal_mean_obj_size(cache_t *cache, double req_obj_size, double *mean_obj_size_in_small, + double *mean_obj_size_in_main, double *mean_obj_size) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + cache_t *small_q = params->small; + cache_t *main_q = params->main; + + double small_q_n_obj = MAX(small_q->get_n_obj(small_q), 1e-8); + double small_q_byte = small_q->get_occupied_byte(small_q); + double main_q_n_obj = MAX(main_q->get_n_obj(main_q), 1e-8); + double main_q_byte = main_q->get_occupied_byte(main_q); + double cache_n_obj = MAX(cache->get_n_obj(cache), 1e-8); + double cache_byte = cache->get_occupied_byte(cache); + + if (mean_obj_size_in_small != NULL) { + *mean_obj_size_in_small = small_q_byte / small_q_n_obj; + } + if (mean_obj_size_in_main != NULL) { + *mean_obj_size_in_main = main_q_byte / main_q_n_obj; + } + if (mean_obj_size != NULL) { + *mean_obj_size = cache_byte / cache_n_obj; + } +} + +/** + * @brief find an object in the cache + * + * @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 *S3FIFOSize_find(cache_t *cache, const request_t *req, const bool update_cache) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + + // if update cache is false, we only check the fifo and main caches + if (!update_cache) { + cache_obj_t *obj = params->small->find(params->small, req, false); + if (obj != NULL) { + return obj; + } + obj = params->main->find(params->main, req, false); + return obj; + } + + /* update cache is true from now */ + cache_obj_t *obj = params->small->find(params->small, req, true); + if (obj != NULL) { +#ifdef USE_FILTER + if (params->n_byte_admit_to_small - obj->S3FIFO.insertion_time > params->small->cache_size / 2) { + obj->S3FIFO.freq += 1; + // } else { + // params->small->get_occupied_byte(params->small)/10000000); + } +#else + obj->S3FIFO.freq += 1; +#endif + return obj; + } + + obj = params->main->find(params->main, req, true); + if (obj != NULL) { + obj->S3FIFO.freq += 1; + } + + return obj; +} + +/** + * @brief insert an object into the cache, + * update the hash table and cache metadata + * this function 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 *S3FIFOSize_insert(cache_t *cache, const request_t *req) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + cache_obj_t *obj = NULL; + + cache_t *main_q = params->main; + cache_t *ghost_q = params->ghost; + + double mean_obj_size_in_small, mean_obj_size_in_main, mean_obj_size; + cal_mean_obj_size(cache, req->obj_size, &mean_obj_size_in_small, &mean_obj_size_in_main, &mean_obj_size); + + cache_obj_t *ghost_obj = ghost_q->find(ghost_q, req, false); + assert(ghost_obj == NULL || ghost_obj->S3FIFO.freq > 0); + + if (ghost_obj != NULL) { + // we need to compare with the small queue, because the object has not had chance to accumulate enough hits in the + // main queue + double ratio = (double)req->obj_size / mean_obj_size_in_small; + // TODO: Why >= not larger than??????????????????? + if ((ghost_obj->S3FIFO.freq) / ratio >= params->move_to_main_threshold) { + // insert to main + params->n_byte_admit_to_main += req->obj_size; + obj = main_q->insert(main_q, req); + obj->S3FIFO.freq = 1; + } else { + // insert to small FIFO + params->n_byte_admit_to_small += req->obj_size; + obj = params->small->insert(params->small, req); + obj->S3FIFO.insertion_time = params->n_byte_admit_to_small; + // only keep the frequency when inserting into the small queue + // the ghost frequency has not been updated + obj->S3FIFO.freq = ghost_obj->S3FIFO.freq + 1; + } + ghost_q->remove(ghost_q, req->obj_id); + } else { + int64_t small_q_byte = params->small->get_occupied_byte(params->small); + int64_t small_q_cache_size = params->small->cache_size; + + // if (!params->has_evicted && small_q_byte >= small_q_cache_size) { + // params->n_byte_admit_to_main += req->obj_size; + // obj = main_q->insert(main_q, req); + // } else { + // params->n_byte_admit_to_small += req->obj_size; + // obj = params->small->insert(params->small, req); + // obj->S3FIFO.insertion_time = params->n_byte_admit_to_small; + // } + + if (!params->has_evicted) { + if (main_q->get_occupied_byte(main_q) + req->obj_size + cache->obj_md_size <= main_q->cache_size) { + params->n_byte_admit_to_main += req->obj_size; + obj = main_q->insert(main_q, req); + } else if (small_q_byte + req->obj_size + cache->obj_md_size >= small_q_cache_size) { + ERROR("both small and main queue are full, but we are not evicting\n"); + } else { + params->n_byte_admit_to_small += req->obj_size; + obj = params->small->insert(params->small, req); + obj->S3FIFO.insertion_time = params->n_byte_admit_to_small; + } + } else { + params->n_byte_admit_to_small += req->obj_size; + obj = params->small->insert(params->small, req); + obj->S3FIFO.insertion_time = params->n_byte_admit_to_small; + } + + obj->S3FIFO.freq = 1; + } + + return obj; +} + +/** + * @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 + * + * @param cache the cache + * @return the object to be evicted + */ +static cache_obj_t *S3FIFOSize_to_evict(cache_t *cache, const request_t *req) { + assert(false); + return NULL; +} + +// evict from FIFO +static void S3FIFOSize_evict_fifo(cache_t *cache, const request_t *req) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + cache_t *small_q = params->small; + cache_t *main_q = params->main; + + cache_obj_t *obj_to_evict = small_q->to_evict(small_q, req); + DEBUG_ASSERT(obj_to_evict != NULL); + // need to copy the object before it is evicted + copy_cache_obj_to_request(params->req_local, obj_to_evict); + + double cache_n_obj = MAX(cache->get_n_obj(cache), 1e-8); + double cache_byte = cache->get_occupied_byte(cache); + + double mean_obj_size = cache_byte / cache_n_obj; + + int64_t obj_size = obj_to_evict->obj_size; + double ratio = (double)obj_size / mean_obj_size; + + if ((obj_to_evict->S3FIFO.freq) / ratio >= params->move_to_main_threshold) { + params->n_byte_move_to_main += obj_to_evict->obj_size; + cache_obj_t *new_obj = main_q->insert(main_q, params->req_local); + new_obj->S3FIFO.freq = 1; + } else { + // insert to ghost + if (params->ghost != NULL) { + params->ghost->get(params->ghost, params->req_local); + cache_obj_t *ghost_obj = params->ghost->find(params->ghost, params->req_local, false); + ghost_obj->S3FIFO.freq = obj_to_evict->S3FIFO.freq; + } + } + + small_q->evict(small_q, params->req_local); +} + +// evict from main cache +static void S3FIFOSize_evict_main(cache_t *cache, const request_t *req) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + cache_t *small_q = params->small; + cache_t *main_q = params->main; + cache_t *ghost_q = params->ghost; + + double cache_n_obj = MAX(cache->get_n_obj(cache), 1e-8); + double cache_byte = cache->get_occupied_byte(cache); + + cache_obj_t *obj_to_evict = main_q->to_evict(main_q, req); + int freq = obj_to_evict->S3FIFO.freq; + copy_cache_obj_to_request(params->req_local, obj_to_evict); + double mean_obj_size = cache_byte / cache_n_obj; + + double ratio = (double)obj_to_evict->obj_size / mean_obj_size; + + bool removed = main_q->remove(main_q, obj_to_evict->obj_id); + DEBUG_ASSERT(removed); + + if ((double)(freq) / ratio >= params->move_to_main_threshold) { + cache_obj_t *new_obj = main_q->insert(main_q, params->req_local); + // clock with 2-bit counter, 4 is better than 3 + new_obj->S3FIFO.freq = MIN(freq, 4) - 1; + params->n_byte_reinsert_to_main += new_obj->obj_size; + } +} + +/** + * @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 + * + * @param cache + * @param req not used + * @param evicted_obj if not NULL, return the evicted object to caller + */ +static void S3FIFOSize_evict(cache_t *cache, const request_t *req) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + params->has_evicted = true; + + cache_t *small_q = params->small; + cache_t *main_q = params->main; + cache_t *ghost_q = params->ghost; + + while (cache->get_occupied_byte(cache) + req->obj_size + cache->obj_md_size > cache->cache_size) { + // printf("req %ld size %ld cache size %ld/%ld + %ld/%ld = %ld, %ld left\n", req->obj_size / 1000, cache->n_req, + // params->small->get_occupied_byte(params->small) / 1000, params->small->cache_size / 1000, + // params->main->get_occupied_byte(params->main) / 1000, params->main->cache_size / 1000, + // (cache->get_occupied_byte(cache) + req->obj_size + cache->obj_md_size) / 1000, + // cache->cache_size / 1000 - cache->get_occupied_byte(cache) / 1000); + + if (main_q->get_occupied_byte(main_q) > main_q->cache_size || small_q->get_occupied_byte(small_q) == 0) { + S3FIFOSize_evict_main(cache, req); + // main_q->evict(main_q, req); + } else { + S3FIFOSize_evict_fifo(cache, req); + } + } +} + +/** + * @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 + * + * @param cache + * @param obj_id + * @return true if the object is removed, false if the object is not in the + * cache + */ +static bool S3FIFOSize_remove(cache_t *cache, const obj_id_t obj_id) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + bool removed = false; + removed = removed || params->small->remove(params->small, obj_id); + removed = removed || (params->ghost && params->ghost->remove(params->ghost, obj_id)); + removed = removed || params->main->remove(params->main, obj_id); + + return removed; +} + +static inline int64_t S3FIFOSize_get_occupied_byte(const cache_t *cache) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + return params->small->get_occupied_byte(params->small) + params->main->get_occupied_byte(params->main); +} + +static inline int64_t S3FIFOSize_get_n_obj(const cache_t *cache) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + return params->small->get_n_obj(params->small) + params->main->get_n_obj(params->main); +} + +static bool can_insert_to_small(const cache_t *cache, const request_t *req) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + +#ifdef PROB_ADMISSION + double r = (double)(req->obj_size) / params->small->cache_size; + if (next_rand_double() < r) { + return false; + } +#else + if (req->obj_size >= params->small->cache_size) { + return false; + } +#endif + return true; +} + +static bool S3FIFOSize_can_insert(cache_t *cache, const request_t *req) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)cache->eviction_params; + + cache_t *small_q = params->small; + cache_t *main_q = params->main; + cache_t *ghost_q = params->ghost; + + double mean_obj_size_in_small, mean_obj_size_in_main, mean_obj_size; + cal_mean_obj_size(cache, req->obj_size, &mean_obj_size_in_small, &mean_obj_size_in_main, &mean_obj_size); + + double ratio = (double)req->obj_size / mean_obj_size_in_small; + + cache_obj_t *ghost_obj = ghost_q->find(ghost_q, req, false); + + if (ghost_obj != NULL) { + if ((ghost_obj->S3FIFO.freq) / ratio >= params->move_to_main_threshold) { + if (req->obj_size >= params->main->cache_size) { + return false; + } + } else { + if (!can_insert_to_small(cache, req)) { + return false; + } + } + } else { + if (!can_insert_to_small(cache, req)) { + return false; + } + } + + return cache_can_insert_default(cache, req); +} + +// *********************************************************************** +// **** **** +// **** parameter set up functions **** +// **** **** +// *********************************************************************** +static const char *S3FIFOSize_current_params(S3FIFOSize_params_t *params) { + static __thread char params_str[128]; + snprintf(params_str, 128, "fifo-size-ratio=%.4lf,ghost-size-ratio=%.4lf,move-to-main-threshold=%d\n", + params->small_size_ratio, params->ghost_size_ratio, params->move_to_main_threshold); + return params_str; +} + +static void S3FIFOSize_parse_params(cache_t *cache, const char *cache_specific_params) { + S3FIFOSize_params_t *params = (S3FIFOSize_params_t *)(cache->eviction_params); + + char *params_str = strdup(cache_specific_params); + char *old_params_str = params_str; + char *end; + + while (params_str != NULL && params_str[0] != '\0') { + /* different parameters are separated by comma, + * key and value are separated by = */ + char *key = strsep((char **)¶ms_str, "="); + char *value = strsep((char **)¶ms_str, ","); + + // skip the white space + while (params_str != NULL && *params_str == ' ') { + params_str++; + } + + if (strcasecmp(key, "fifo-size-ratio") == 0 || strcasecmp(key, "small-size-ratio") == 0 || + strcasecmp(key, "small-queue-size") == 0) { + params->small_size_ratio = strtod(value, NULL); + } else if (strcasecmp(key, "ghost-size-ratio") == 0) { + params->ghost_size_ratio = strtod(value, NULL); + } else if (strcasecmp(key, "move-to-main-threshold") == 0) { + params->move_to_main_threshold = atoi(value); + } else if (strcasecmp(key, "print") == 0) { + printf("parameters: %s\n", S3FIFOSize_current_params(params)); + exit(0); + } else { + ERROR("%s does not have parameter %s\n", cache->cache_name, key); + exit(1); + } + } + + free(old_params_str); +} + +#ifdef __cplusplus +} +#endif diff --git a/libCacheSim/cache/eviction/cpp/GDSF_compute.cpp b/libCacheSim/cache/eviction/cpp/GDSF_compute.cpp new file mode 100644 index 00000000..ef35fdb6 --- /dev/null +++ b/libCacheSim/cache/eviction/cpp/GDSF_compute.cpp @@ -0,0 +1,310 @@ +/* GDSF_compute: greedy dual frequency size with compute */ + +#include +#include + +#include "abstractRank.hpp" + +namespace eviction { +class GDSF_compute : public abstractRank { + public: + GDSF_compute() = default; + + double pri_last_evict = 0.0; +}; +} // namespace eviction + +#ifdef __cplusplus +extern "C" { +#endif + +// *********************************************************************** +// **** **** +// **** function declarations **** +// **** **** +// *********************************************************************** + +cache_t *GDSF_compute_init(const common_cache_params_t ccache_params, + const char *cache_specific_params); +static void GDSF_compute_free(cache_t *cache); +static bool GDSF_compute_get(cache_t *cache, const request_t *req); + +static cache_obj_t *GDSF_compute_find(cache_t *cache, const request_t *req, + const bool update_cache); +static cache_obj_t *GDSF_compute_insert(cache_t *cache, const request_t *req); +static cache_obj_t *GDSF_compute_to_evict(cache_t *cache, const request_t *req); +static void GDSF_compute_evict(cache_t *cache, const request_t *req); + +static bool GDSF_compute_remove(cache_t *cache, const obj_id_t obj_id); + +// *********************************************************************** +// **** **** +// **** end user facing functions **** +// **** **** +// **** init, free, get **** +// *********************************************************************** + +/** + * @brief initialize the cache + * + * @param ccache_params some common cache parameters + * @param cache_specific_params cache specific parameters, see parse_params + * function or use -e "print" with the cachesim binary + */ +cache_t *GDSF_compute_init(const common_cache_params_t ccache_params, + const char *cache_specific_params) { + cache_t *cache = + cache_struct_init("GDSF_compute", ccache_params, cache_specific_params); + cache->eviction_params = reinterpret_cast(new eviction::GDSF_compute); + + cache->cache_init = GDSF_compute_init; + cache->cache_free = GDSF_compute_free; + cache->get = GDSF_compute_get; + cache->find = GDSF_compute_find; + cache->insert = GDSF_compute_insert; + cache->evict = GDSF_compute_evict; + cache->to_evict = GDSF_compute_to_evict; + cache->remove = GDSF_compute_remove; + + if (ccache_params.consider_obj_metadata) { + // freq + priority + cache->obj_md_size = 8; + } else { + cache->obj_md_size = 0; + } + + return cache; +} + +/** + * free resources used by this cache + * + * @param cache + */ +static void GDSF_compute_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 GDSF_compute_get(cache_t *cache, const request_t *req) { + auto *gdsf = reinterpret_cast(cache->eviction_params); + cache_obj_t *obj = cache->find(cache, req, true); + bool hit = (obj != NULL); + + if (!hit && cache->can_insert(cache, req)) { + cache->insert(cache, req); + while (cache->get_occupied_byte(cache) > cache->cache_size) { + cache->evict(cache, req); + } + } + + DEBUG_ASSERT((int64_t)gdsf->pq.size() == cache->n_obj); + DEBUG_ASSERT((int64_t)gdsf->pq_map.size() == cache->n_obj); + + return hit; +} + +// *********************************************************************** +// **** **** +// **** 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, + * 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 *GDSF_compute_find(cache_t *cache, const request_t *req, + const bool update_cache) { + cache->n_req += 1; + + auto *gdsf = reinterpret_cast(cache->eviction_params); + cache_obj_t *obj = cache_find_base(cache, req, update_cache); + /* this does not consider object size change */ + if (obj != nullptr && update_cache) { + /* misc frequency is updated in cache_find_base */ + // obj->misc.freq += 1; + + auto node = gdsf->pq_map[obj]; + gdsf->pq.erase(node); + + double pri = gdsf->pri_last_evict + (double)(obj->misc.freq) * sqrt((double)(obj->GDSF_compute.compute_intensity)); + // double pri = gdsf->pri_last_evict + (double)(obj->misc.freq) * obj->GDSF_compute.compute_intensity; + // double pri = gdsf->pri_last_evict + (double)(obj->misc.freq) * (double)(obj->GDSF_compute.compute_intensity) / (1000 + obj->GDSF_compute.compute_intensity); + eviction::pq_node_type new_node = {obj, pri, cache->n_req}; + gdsf->pq.insert(new_node); + gdsf->pq_map[obj] = new_node; + } + + return obj; +} + +static bool GDSF_compute_can_insert(cache_t *cache, const request_t *req) { + static __thread int64_t n_insert = 0, n_cannot_insert = 0; + auto *gdsf = reinterpret_cast(cache->eviction_params); + if (cache->get_occupied_byte(cache) + req->obj_size <= cache->cache_size) { + return true; + } + if (req->obj_size > cache->cache_size) { + return false; + } + + int64_t to_evict_size = + req->obj_size - (cache->cache_size - cache->get_occupied_byte(cache)); + double pri = gdsf->pri_last_evict + req->features[0]; + bool can_insert = true; + auto iter = gdsf->pq.begin(); + + int n_evict = 0; + while (to_evict_size > 0) { + assert(iter != gdsf->pq.end()); + assert(iter->obj->obj_id != req->obj_id); + n_evict += 1; + + if (iter->priority > pri) { + // the incoming object will be evicted so not insert it + can_insert = false; + break; + } + to_evict_size -= iter->obj->obj_size; + iter++; + } + + if (can_insert) { + n_insert += 1; + } else { + n_cannot_insert += 1; + } + + if ((n_insert + n_cannot_insert) % 100000 == 0) { + if ((double)n_cannot_insert / (n_insert + n_cannot_insert) > 0.01) + DEBUG("size %lld n_insert %lld, n_cannot_insert %lld, ratio %.2f\n", + (long long)cache->cache_size, (long long)n_insert, + (long long)n_cannot_insert, + (double)n_cannot_insert / (n_insert + n_cannot_insert)); + } + + return can_insert; +} + +/** + * @brief insert an object into the cache, + * update the hash table and cache metadata + * this function 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 *GDSF_compute_insert(cache_t *cache, const request_t *req) { + auto *gdsf = reinterpret_cast(cache->eviction_params); + + // this does not affect insertion for most workloads unless object size is too + // large however, when it have effect, it often increases miss ratio because a + // list of small objects (with relatively large priority) will stop the + // insertion of a large object, however, the newly requested large object is + // likely to be more useful than the small objects + // if (!GDSF_compute_can_insert(cache, req)) return nullptr; + + cache_obj_t *obj = cache_insert_base(cache, req); + DEBUG_ASSERT(obj != nullptr); + obj->misc.freq = 1; + obj->GDSF_compute.compute_intensity = req->features[0]; + + double pri = gdsf->pri_last_evict + req->features[0]; + eviction::pq_node_type new_node = {obj, pri, cache->n_req}; + auto r = gdsf->pq.insert(new_node); + DEBUG_ASSERT(r.second); + gdsf->pq_map[obj] = new_node; + + return obj; +} + +/** + * @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 + * + * @param cache the cache + * @return the object to be evicted + */ +static cache_obj_t *GDSF_compute_to_evict(cache_t *cache, const request_t *req) { + auto *gdsf = reinterpret_cast(cache->eviction_params); + eviction::pq_node_type p = gdsf->peek_lowest_score(); + + return p.obj; +} + +/** + * @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 + * + * @param cache + * @param req not used + * @param evicted_obj if not NULL, return the evicted object to caller + */ +static void GDSF_compute_evict(cache_t *cache, const request_t *req) { + auto *gdsf = reinterpret_cast(cache->eviction_params); + eviction::pq_node_type p = gdsf->pop_lowest_score(); + cache_obj_t *obj = p.obj; + + gdsf->pri_last_evict = p.priority; + cache_remove_obj_base(cache, obj, true); +} + +static void GDSF_compute_remove_obj(cache_t *cache, cache_obj_t *obj) { + auto *gdsf = reinterpret_cast(cache->eviction_params); + gdsf->remove_obj(cache, obj); +} + +/** + * @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 + * + * @param cache + * @param obj_id + * @return true if the object is removed, false if the object is not in the + * cache + */ +static bool GDSF_compute_remove(cache_t *cache, const obj_id_t obj_id) { + auto *gdsf = reinterpret_cast(cache->eviction_params); + return gdsf->remove(cache, obj_id); +} + +#ifdef __cplusplus +} +#endif diff --git a/libCacheSim/include/libCacheSim/cache.h b/libCacheSim/include/libCacheSim/cache.h index 575b05ec..ff71c288 100644 --- a/libCacheSim/include/libCacheSim/cache.h +++ b/libCacheSim/include/libCacheSim/cache.h @@ -25,6 +25,13 @@ extern "C" { #endif +// #define RECORD_EVICTION_PROCESS 1 + +#ifdef RECORD_EVICTION_PROCESS +void set_new_record_eviction_process_file(const char *ofilepath); +void print_eviction_debug_message(const char *msg); +#endif /* RECORD_EVICTION_PROCESS */ + struct cache; typedef struct cache cache_t; diff --git a/libCacheSim/include/libCacheSim/cacheObj.h b/libCacheSim/include/libCacheSim/cacheObj.h index 3fe3ea7b..f1d2ffe5 100644 --- a/libCacheSim/include/libCacheSim/cacheObj.h +++ b/libCacheSim/include/libCacheSim/cacheObj.h @@ -75,6 +75,7 @@ typedef struct { typedef struct Belady_obj_metadata { void *pq_node; int64_t next_access_vtime; + int32_t compute_intensity; // for BeladyCompute algorithm } Belady_obj_metadata_t; typedef struct { @@ -143,6 +144,10 @@ typedef struct { int32_t freq; } __attribute__((packed)) Sieve_obj_params_t; +typedef struct { + int32_t compute_intensity; +} GDSF_compute_obj_metadata_t; + typedef struct { int64_t next_access_vtime; int32_t freq; @@ -191,6 +196,7 @@ typedef struct cache_obj { S3FIFO_obj_metadata_t S3FIFO; Sieve_obj_params_t sieve; CAR_obj_metadata_t CAR; + GDSF_compute_obj_metadata_t GDSF_compute; #if defined(ENABLE_GLCACHE) && ENABLE_GLCACHE == 1 GLCache_obj_metadata_t GLCache; diff --git a/libCacheSim/include/libCacheSim/evictionAlgo.h b/libCacheSim/include/libCacheSim/evictionAlgo.h index ffea3ff2..6a5c4afd 100644 --- a/libCacheSim/include/libCacheSim/evictionAlgo.h +++ b/libCacheSim/include/libCacheSim/evictionAlgo.h @@ -50,6 +50,9 @@ cache_t *Belady_init(const common_cache_params_t ccache_params, cache_t *BeladySize_init(const common_cache_params_t ccache_params, const char *cache_specific_params); +cache_t *BeladyCompute_init(const common_cache_params_t ccache_params, + const char *cache_specific_params); + cache_t *CAR_init(const common_cache_params_t ccache_params, const char *cache_specific_params); @@ -80,6 +83,9 @@ cache_t *flashProb_init(const common_cache_params_t ccache_params, cache_t *GDSF_init(const common_cache_params_t ccache_params, const char *cache_specific_params); +cache_t *GDSF_compute_init(const common_cache_params_t ccache_params, + const char *cache_specific_params); + cache_t *Hyperbolic_init(const common_cache_params_t ccache_params, const char *cache_specific_params); @@ -101,6 +107,9 @@ cache_t *LFUDA_init(const common_cache_params_t ccache_params, cache_t *LHD_init(const common_cache_params_t ccache_params, const char *cache_specific_params); +cache_t *LHD_compute_init(const common_cache_params_t ccache_params, + const char *cache_specific_params); + cache_t *LIRS_init(const common_cache_params_t ccache_params, const char *cache_specific_params); @@ -138,6 +147,9 @@ cache_t *Random_init(const common_cache_params_t ccache_params, cache_t *S3FIFO_init(const common_cache_params_t ccache_params, const char *cache_specific_params); +cache_t *S3FIFOCompute_init(const common_cache_params_t ccache_params, + const char *cache_specific_params); + cache_t *S3FIFOd_init(const common_cache_params_t ccache_params, const char *cache_specific_params);