Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions libCacheSim/cache/eviction/SLRU.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ typedef struct SLRU_params {
int64_t *lru_n_objs;
int64_t *lru_max_n_bytes;
int n_seg;
double seg_ratio[SLRU_MAX_N_SEG];
} SLRU_params_t;

// ***********************************************************************
Expand Down Expand Up @@ -140,6 +141,7 @@ cache_t *SLRU_init(const common_cache_params_t ccache_params,
params->lru_max_n_bytes[i] =
(int64_t)(ccache_params.cache_size / params->n_seg) +
(i >= params->n_seg - remainder);
params->seg_ratio[i] = 1.0 / params->n_seg;
}
}

Expand Down Expand Up @@ -261,9 +263,11 @@ static cache_obj_t *SLRU_find(cache_t *cache, const request_t *req,
}
#endif

if (obj->SLRU.lru_id == params->n_seg - 1) {
move_obj_to_head(&params->lru_heads[params->n_seg - 1],
&params->lru_tails[params->n_seg - 1], obj);
int id = obj->SLRU.lru_id;
if (id == params->n_seg - 1 ||
obj->obj_size + cache->obj_md_size > params->lru_max_n_bytes[id + 1]) {
// an object larger than the next segment (after SLRU_resize) stays here
move_obj_to_head(&params->lru_heads[id], &params->lru_tails[id], obj);
} else {
SLRU_promote_to_next_seg(cache, req, obj);

Expand Down Expand Up @@ -489,9 +493,9 @@ static void SLRU_parse_params(cache_t *cache,
params->n_seg = n_seg;
params->lru_max_n_bytes = calloc(params->n_seg, sizeof(int64_t));
for (int i = 0; i < n_seg; i++) {
params->seg_ratio[i] = (double)seg_size_array[i] / seg_size_sum;
params->lru_max_n_bytes[i] =
(int64_t)((double)seg_size_array[i] / seg_size_sum *
cache->cache_size);
(int64_t)(params->seg_ratio[i] * cache->cache_size);
if (params->lru_max_n_bytes[i] <= 0) {
ERROR("Invalid segment size for segment %d: %lld bytes\n", i,
(long long)params->lru_max_n_bytes[i]);
Expand All @@ -514,6 +518,18 @@ static void SLRU_parse_params(cache_t *cache,
// **** internal functions ****
// **** ****
// ***********************************************************************
/* change the cache size and keep the share of each segment, no eviction */
void SLRU_resize(cache_t *cache, int64_t new_size) {
SLRU_params_t *params = (SLRU_params_t *)(cache->eviction_params);
int64_t n_assigned = 0;
for (int i = 0; i < params->n_seg - 1; i++) {
params->lru_max_n_bytes[i] = (int64_t)(params->seg_ratio[i] * new_size);
n_assigned += params->lru_max_n_bytes[i];
}
params->lru_max_n_bytes[params->n_seg - 1] = new_size - n_assigned;
cache->cache_size = new_size;
}

/* SLRU cannot insert an object larger than segment size */
bool SLRU_can_insert(cache_t *cache, const request_t *req) {
SLRU_params_t *params = (SLRU_params_t *)cache->eviction_params;
Expand Down
87 changes: 83 additions & 4 deletions libCacheSim/cache/eviction/WTinyLFU.c
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
// combined with a windowed LRU structure (W-TinyLFU)
// to resist burst traffic of temporal hot objects
//
// hill-climb=1 adapts the window size with the hill climber in Adaptive
// Software Cache Management (Middleware'18): every 10 x cache size bytes of
// requests, move the window by 5% (1% to 80%), and turn around if the hit
// ratio dropped. Different from the paper, the window does not go to 0%
// (every request would miss), a tie does not turn around (as in Caffeine),
// and the period is in bytes (the same for unit-size objects)
//
// NOTE: it is NOT thread-safe
//
// part of the details borrowed from https://github.com/mandreyel/w-tinylfu
Expand Down Expand Up @@ -41,10 +48,23 @@ typedef struct WTinyLFU_params {
char main_cache_type[32];

request_t *req_local;

bool hill_climb;
int window_step;
int climb_direction;
int64_t n_climb_req;
int64_t n_climb_hit;
int64_t n_climb_byte;
double prev_hit_ratio;
} WTinyLFU_params_t;

static const char *DEFAULT_PARAMS = "main-cache=SLRU,window-size=0.01";

#define MAX_WINDOW_STEP 16
static double WTinyLFU_window_ratio(int step) {
return step == 0 ? 0.01 : 0.05 * step;
}

// ***********************************************************************
// **** ****
// **** function declarations ****
Expand Down Expand Up @@ -110,6 +130,17 @@ cache_t *WTinyLFU_init(const common_cache_params_t ccache_params,
WTinyLFU_parse_params(cache, cache_specific_params);
}

if (params->hill_climb) {
if (strcasecmp(params->main_cache_type, "LRU") != 0 &&
strcasecmp(params->main_cache_type, "SLRU") != 0) {
ERROR("WTinyLFU hill-climb only supports LRU and SLRU main caches\n");
}
params->window_step =
(int)MIN(lround(params->window_size / 0.05), MAX_WINDOW_STEP);
params->window_size = WTinyLFU_window_ratio(params->window_step);
params->climb_direction = 1;
}

common_cache_params_t ccache_params_local = ccache_params;

ccache_params_local.cache_size *= params->window_size;
Expand Down Expand Up @@ -163,8 +194,9 @@ cache_t *WTinyLFU_init(const common_cache_params_t ccache_params,
cache->obj_md_size = params->LRU->obj_md_size;
}

snprintf(cache->cache_name, CACHE_NAME_ARRAY_LEN, "WTinyLFU-w%.2lf-%s",
params->window_size, params->main_cache_type);
snprintf(cache->cache_name, CACHE_NAME_ARRAY_LEN, "WTinyLFU-w%.2lf-%s%s",
params->window_size, params->main_cache_type,
params->hill_climb ? "-climb" : "");

params->req_local = new_request();
params->n_admit_bytes = 0;
Expand Down Expand Up @@ -216,12 +248,53 @@ static void WTinyLFU_free(cache_t *cache) {
cache_struct_free(cache);
}

static void WTinyLFU_resize_window(cache_t *cache, const request_t *req) {
WTinyLFU_params_t *params = (WTinyLFU_params_t *)(cache->eviction_params);
cache_t *main_cache = params->main_cache;
double window_ratio = WTinyLFU_window_ratio(params->window_step);
params->LRU->cache_size = (int64_t)(cache->cache_size * window_ratio);
int64_t main_size = cache->cache_size - params->LRU->cache_size;
if (strcasecmp(params->main_cache_type, "SLRU") == 0) {
SLRU_resize(main_cache, main_size);
} else {
main_cache->cache_size = main_size;
}
while (main_cache->get_occupied_byte(main_cache) > main_cache->cache_size) {
main_cache->evict(main_cache, req);
}
}

static void WTinyLFU_climb(cache_t *cache, const request_t *req, bool hit) {
WTinyLFU_params_t *params = (WTinyLFU_params_t *)(cache->eviction_params);
params->n_climb_req += 1;
params->n_climb_hit += hit;
params->n_climb_byte += req->obj_size;
if (params->n_climb_byte < 10 * cache->cache_size) {
return;
}

double hit_ratio = (double)params->n_climb_hit / params->n_climb_req;
if (hit_ratio < params->prev_hit_ratio) {
params->climb_direction = -params->climb_direction;
}
params->prev_hit_ratio = hit_ratio;
params->n_climb_req = params->n_climb_hit = params->n_climb_byte = 0;

params->window_step += params->climb_direction;
params->window_step = MAX(0, MIN(MAX_WINDOW_STEP, params->window_step));
WTinyLFU_resize_window(cache, req);
}

static bool WTinyLFU_get(cache_t *cache, const request_t *req) {
/* because this field cannot be updated in time since segment LRUs are
* updated, so we should not use this field */
DEBUG_ASSERT(cache->occupied_byte == 0);
WTinyLFU_params_t *params = (WTinyLFU_params_t *)(cache->eviction_params);

bool ck = cache_get_base(cache, req);
if (params->hill_climb) {
WTinyLFU_climb(cache, req, ck);
}
return ck;
}

Expand Down Expand Up @@ -383,10 +456,10 @@ static bool WTinyLFU_remove(cache_t *cache, obj_id_t obj_id) {
* configured type, which is what `-e print` runs against */
static const char *WTinyLFU_current_params(WTinyLFU_params_t *params) {
static __thread char params_str[128];
snprintf(params_str, 128, "main-cache=%s,window-size=%.4lf",
snprintf(params_str, 128, "main-cache=%s,window-size=%.4lf,hill-climb=%d",
params->main_cache == NULL ? params->main_cache_type
: params->main_cache->cache_name,
params->window_size);
params->window_size, params->hill_climb);
return params_str;
}

Expand Down Expand Up @@ -418,6 +491,12 @@ static void WTinyLFU_parse_params(cache_t *cache,
error = "expected a nonempty value";
} else if (strcasecmp(key, "main-cache") == 0) {
strncpy(params->main_cache_type, value, 30);
} else if (strcasecmp(key, "hill-climb") == 0) {
if (strcmp(value, "0") != 0 && strcmp(value, "1") != 0) {
error = "expected 0 or 1";
} else {
params->hill_climb = value[0] == '1';
}
} else if (strcasecmp(key, "window-size") == 0) {
char *end;
errno = 0;
Expand Down
2 changes: 2 additions & 0 deletions libCacheSim/include/libCacheSim/evictionAlgo.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ cache_t *Size_init(const common_cache_params_t ccache_params,
cache_t *SLRU_init(const common_cache_params_t ccache_params,
const char *cache_specific_params);

void SLRU_resize(cache_t *cache, int64_t new_size);

cache_t *SLRUv0_init(const common_cache_params_t ccache_params,
const char *cache_specific_params);

Expand Down
12 changes: 10 additions & 2 deletions test/test_cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,8 @@ for params in main-cache main-cache= main-cache,print \
window-size=abc,print window-size=0.1junk,print window-size=nan \
window-size=nan,print window-size=inf,print window-size=-inf,print \
window-size=-0.1,print window-size=1,print window-size=1e999,print \
window-size=1e-999,print unknown=1; do
window-size=1e-999,print hill-climb hill-climb= hill-climb=2,print \
hill-climb=yes,print unknown=1; do
out=$("${BIN_DIR}/cachesim" "${TRACE}" vscsi wtinyLFU 1mb \
--num-req=10 --hashpower=8 -e "${params}" 2>&1)
rc=$?
Expand All @@ -320,7 +321,8 @@ for params in main-cache main-cache= main-cache,print \
fi
done
for params in '' window-size=0.01 window-size=1e-2 window-size=0.01, \
'main-cache=LRU,window-size=0.01' 'main-cache=FIFO, window-size=0.01'; do
'main-cache=LRU,window-size=0.01' 'main-cache=FIFO, window-size=0.01' \
hill-climb=0 hill-climb=1 'main-cache=LRU,hill-climb=1'; do
expect_output "WTinyLFU accepts '${params}'" '10 req' \
"${BIN_DIR}/cachesim" "${TRACE}" vscsi tinyLFU 1mb \
--num-req=10 --hashpower=8 -e "${params}"
Expand All @@ -333,6 +335,12 @@ for spec in 0:0.0000 0.01:0.0100 1e-2:0.0100 +0.01:0.0100 \
"${BIN_DIR}/cachesim" "${TRACE}" vscsi wtinyLFU 1mb \
--hashpower=8 -e "window-size=${value},print"
done
expect_output "WTinyLFU reports hill-climb=1" "hill-climb=1" \
"${BIN_DIR}/cachesim" "${TRACE}" vscsi wtinyLFU 1mb \
--hashpower=8 -e "hill-climb=1,print"
expect_clean_error "WTinyLFU hill-climb rejects a FIFO main cache" \
"${BIN_DIR}/cachesim" "${TRACE}" vscsi wtinyLFU 1mb \
--num-req=10 --hashpower=8 -e "main-cache=FIFO,hill-climb=1"

echo "running admission and prefetch missing-value tests"

Expand Down
107 changes: 107 additions & 0 deletions test/test_evictionAlgo.c
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,106 @@ static void test_WTinyLFU_counts_window_hits(void) {
cache->cache_free(cache);
}

// The first hill climbing decision, after 10 x cache size requests, grows the
// window from 1% to 5%, and the main cache evicts down to its new size.
static void test_WTinyLFU_hill_climb_grows_window(void) {
common_cache_params_t cc_params = {.cache_size = 100, .hashpower = 8};
cache_t *cache = WTinyLFU_init(cc_params, "main-cache=LRU,hill-climb=1");
request_t *req = new_request();
req->obj_size = 1;

// 101 moves 1 to 99 into the main cache
for (obj_id_t id = 1; id <= 101; id++) {
req->obj_id = id;
g_assert_false(cache->get(cache, req));
}
g_assert_cmpint(cache->get_n_obj(cache), ==, 100);

// hits on the main cache until the 1000th request
for (int i = 0; i < 899; i++) {
req->obj_id = 2 + i % 98;
g_assert_true(cache->get(cache, req));
}

// the main cache is now 95 objects instead of 99
g_assert_cmpint(cache->get_n_obj(cache), ==, 96);

// the window grows into the free space
for (obj_id_t id = 102; id <= 105; id++) {
req->obj_id = id;
g_assert_false(cache->get(cache, req));
}
g_assert_cmpint(cache->get_n_obj(cache), ==, 100);

free_request(req);
cache->cache_free(cache);
}

// A period without hits must not trap the climber: with 19 objects the 1% and
// 5% windows hold no object, so it has to keep growing the window to 10%.
static void test_WTinyLFU_hill_climb_no_hits(void) {
common_cache_params_t cc_params = {.cache_size = 19, .hashpower = 8};
cache_t *cache = WTinyLFU_init(cc_params, "main-cache=LRU,hill-climb=1");
request_t *req = new_request();
req->obj_size = 1;

int64_t n_hit = 0;
for (int i = 0; i < 1000; i++) {
req->obj_id = 1 + i % 5;
bool hit = cache->get(cache, req);
// the first two periods of 190 requests run with an empty window
if (i < 380) g_assert_false(hit);
n_hit += hit;
}
g_assert_cmpint(n_hit, >, 0);

free_request(req);
cache->cache_free(cache);
}

// SLRU_resize keeps the 1:4 shares: the first segment, which caps the object
// size, is 14 at size 71, 6 at size 30, and 14 again back at size 71.
static void test_SLRU_resize(void) {
common_cache_params_t cc_params = {.cache_size = 71, .hashpower = 8};
cache_t *cache = SLRU_init(cc_params, "seg-size=1:4");
request_t *req = new_request();
req->obj_id = 1;

SLRU_resize(cache, 30);
g_assert_cmpint(cache->cache_size, ==, 30);
req->obj_size = 6;
g_assert_true(cache->can_insert(cache, req));
req->obj_size = 7;
g_assert_false(cache->can_insert(cache, req));

SLRU_resize(cache, 71);
req->obj_size = 14;
g_assert_true(cache->can_insert(cache, req));
req->obj_size = 15;
g_assert_false(cache->can_insert(cache, req));

free_request(req);
cache->cache_free(cache);
}

// After SLRU_resize an object can be larger than the next segment. A hit must
// keep it, not promote it, cool it back down and evict it.
static void test_SLRU_resize_keeps_large_object(void) {
common_cache_params_t cc_params = {.cache_size = 100, .hashpower = 8};
cache_t *cache = SLRU_init(cc_params, "seg-size=1:4");
request_t *req = new_request();
req->obj_id = 1;
req->obj_size = 20;

g_assert_false(cache->get(cache, req));
SLRU_resize(cache, 20); // segments of 4 and 16 bytes
g_assert_true(cache->get(cache, req));
g_assert_nonnull(cache->find(cache, req, false));

free_request(req);
cache->cache_free(cache);
}

/* The MINISIM profiler only knows an algorithm by its name and reaches it
* through create_cache_using_plugin(). That used to go straight to dlsym(),
* which cannot see a built-in constructor in a statically linked build — the
Expand Down Expand Up @@ -725,6 +825,13 @@ int main(int argc, char *argv[]) {
test_Clock2QPlus);
g_test_add_func("/libCacheSim/cacheAlgo_WTinyLFU_counts_window_hits",
test_WTinyLFU_counts_window_hits);
g_test_add_func("/libCacheSim/cacheAlgo_WTinyLFU_hill_climb_grows_window",
test_WTinyLFU_hill_climb_grows_window);
g_test_add_func("/libCacheSim/cacheAlgo_WTinyLFU_hill_climb_no_hits",
test_WTinyLFU_hill_climb_no_hits);
g_test_add_func("/libCacheSim/cacheAlgo_SLRU_resize", test_SLRU_resize);
g_test_add_func("/libCacheSim/cacheAlgo_SLRU_resize_keeps_large_object",
test_SLRU_resize_keeps_large_object);

#if defined(ENABLE_3L_CACHE) && ENABLE_3L_CACHE == 1
g_test_add_data_func("/libCacheSim/cacheAlgo_3LCache", reader, test_3LCache);
Expand Down
Loading