Skip to content

Commit fa4e0cf

Browse files
committed
Refactor ARC into LFU and integrate mpool
We have implemented an adaptive replacement cache to reduce memory usage. However, the complexity of the replacement policy of ARC can lead to a decrease in overall performance. As a result, we implemented the LFU cache, which performed better, but we still retained the ARC for future use. Additionally, we imported the memory pool we developed to limit the memory usage of both caches. The statistics below illustrate the performance of the LFU cache and ARC while running the CoreMark benchmark, indicating that the LFU cache outperforms ARC. | Test | CoreMark(Iterations/Sec) | |------+----------------------------| | ARC | 1123.776 | | LFU | 1155.174 |
1 parent bcdc953 commit fa4e0cf

27 files changed

+3801
-18
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ jobs:
1818
make check
1919
make tests
2020
make misalign
21+
make ENABLE_ARC=1 clean all
22+
make ENABLE_ARC=1 tests
2123
- name: diverse configurations
2224
run: |
2325
make distclean ENABLE_EXT_C=0 check

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ ifeq ($(call has, EXT_F), 1)
4040
LDFLAGS += -lm
4141
endif
4242

43+
# Enable adaptive replacement cache policy, default is LRU
44+
ENABLE_ARC ?= 0
45+
$(call set-feature, ARC)
46+
4347
# Experimental SDL oriented system calls
4448
ENABLE_SDL ?= 1
4549
ifeq ($(call has, SDL), 1)

mk/tests.mk

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,23 @@ CACHE_OBJS := $(addprefix $(CACHE_BUILD_DIR)/, $(CACHE_OBJS))
99
OBJS += $(CACHE_OBJS)
1010
deps += $(CACHE_OBJS:%.o=%.o.d)
1111

12-
12+
# Check adaptive replacement cache policy is enabled or not, default is LRU
13+
ifeq ($(ENABLE_ARC), 1)
14+
CACHE_CHECK_ELF_FILES := \
15+
arc/cache-new \
16+
arc/cache-put \
17+
arc/cache-get \
18+
arc/cache-lru-replace \
19+
arc/cache-lfu-replace \
20+
arc/cache-lru-ghost-replace \
21+
arc/cache-lfu-ghost-replace
22+
else
1323
CACHE_CHECK_ELF_FILES := \
14-
cache-new \
15-
cache-put \
16-
cache-get \
17-
cache-lru-replace \
18-
cache-lfu-replace \
19-
cache-lru-ghost-replace \
20-
cache-lfu-ghost-replace
24+
lfu/cache-new \
25+
lfu/cache-put \
26+
lfu/cache-get \
27+
lfu/cache-lfu-replace
28+
endif
2129

2230
CACHE_OUT = $(addprefix $(CACHE_BUILD_DIR)/, $(CACHE_CHECK_ELF_FILES:%=%.out))
2331

@@ -39,9 +47,9 @@ $(CACHE_OUT): $(TARGET)
3947

4048
$(TARGET): $(CACHE_OBJS)
4149
$(VECHO) " CC\t$@\n"
42-
$(Q)$(CC) $^ build/cache.o -o $(CACHE_BUILD_DIR)/$(TARGET)
50+
$(Q)$(CC) $^ build/cache.o build/mpool.o -o $(CACHE_BUILD_DIR)/$(TARGET)
4351

4452
$(CACHE_BUILD_DIR)/%.o: $(CACHE_TEST_DIR)/%.c
4553
$(VECHO) " CC\t$@\n"
46-
$(Q)mkdir -p $(dir $@)
54+
$(Q)mkdir -p $(dir $@)/arc $(dir $@)/lfu
4755
$(Q)$(CC) -o $@ $(CFLAGS) -I./src -c -MMD -MF $@.d $<

src/cache.c

Lines changed: 135 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,20 @@
1010
#include <string.h>
1111

1212
#include "cache.h"
13+
#include "mpool.h"
1314

1415
#define MIN(a, b) ((a < b) ? a : b)
1516
#define GOLDEN_RATIO_32 0x61C88647
1617
#define HASH(val) \
1718
(((val) * (GOLDEN_RATIO_32)) >> (32 - (cache_size_bits))) & (cache_size - 1)
19+
/* THRESHOLD is set to identify hot spots. Once the frequency of use for a block
20+
* exceeds the THRESHOLD, it will be translated into machine code.*/
21+
#define THRESHOLD 1000
1822

1923
static uint32_t cache_size, cache_size_bits;
24+
static struct mpool *cache_mp;
2025

26+
#if RV32_HAS(ARC)
2127
/*
2228
* Adaptive Replacement Cache (ARC) improves the fundamental LRU strategy
2329
* by dividing the cache into two lists, T1 and T2. list T1 is for LRU
@@ -37,7 +43,7 @@ typedef enum {
3743
LFU_ghost_list,
3844
N_CACHE_LIST_TYPES
3945
} cache_list_t;
40-
46+
#endif
4147
struct list_head {
4248
struct list_head *prev, *next;
4349
};
@@ -50,6 +56,7 @@ struct hlist_node {
5056
struct hlist_node *next, **pprev;
5157
};
5258

59+
#if RV32_HAS(ARC)
5360
/*
5461
* list maintains four cache lists T1, T2, B1, and B2.
5562
* ht_list maintains hashtable and improves the performance of cache searching.
@@ -61,17 +68,31 @@ typedef struct {
6168
struct list_head list;
6269
struct hlist_node ht_list;
6370
} arc_entry_t;
71+
#else
72+
typedef struct {
73+
void *value;
74+
uint32_t key;
75+
uint32_t frequency;
76+
struct list_head list;
77+
struct hlist_node ht_list;
78+
} lfu_entry_t;
79+
#endif
6480

6581
typedef struct {
6682
struct hlist_head *ht_list_head;
6783
} hashtable_t;
6884

6985
typedef struct cache {
86+
#if RV32_HAS(ARC)
7087
struct list_head *lists[N_CACHE_LIST_TYPES];
7188
uint32_t list_size[N_CACHE_LIST_TYPES];
89+
uint32_t lru_capacity;
90+
#else
91+
struct list_head *lists[THRESHOLD];
92+
uint32_t list_size;
93+
#endif
7294
hashtable_t *map;
7395
uint32_t capacity;
74-
uint32_t lru_capacity;
7596
} cache_t;
7697

7798
static inline void INIT_LIST_HEAD(struct list_head *head)
@@ -80,6 +101,13 @@ static inline void INIT_LIST_HEAD(struct list_head *head)
80101
head->prev = head;
81102
}
82103

104+
#if !RV32_HAS(ARC)
105+
static inline int list_empty(const struct list_head *head)
106+
{
107+
return (head->next == head);
108+
}
109+
#endif
110+
83111
static inline void list_add(struct list_head *node, struct list_head *head)
84112
{
85113
struct list_head *next = head->next;
@@ -107,6 +135,9 @@ static inline void list_del_init(struct list_head *node)
107135

108136
#define list_entry(node, type, member) container_of(node, type, member)
109137

138+
#define list_first_entry(head, type, member) \
139+
list_entry((head)->next, type, member)
140+
110141
#define list_last_entry(head, type, member) \
111142
list_entry((head)->prev, type, member)
112143

@@ -194,14 +225,15 @@ static inline void hlist_del_init(struct hlist_node *n)
194225
pos = hlist_entry_safe((pos)->member.next, type, member))
195226
#endif
196227

228+
197229
cache_t *cache_create(int size_bits)
198230
{
199231
cache_t *cache = malloc(sizeof(cache_t));
200232
if (!cache)
201233
return NULL;
202234
cache_size_bits = size_bits;
203235
cache_size = 1 << size_bits;
204-
236+
#if RV32_HAS(ARC)
205237
for (int i = 0; i < N_CACHE_LIST_TYPES; i++) {
206238
cache->lists[i] = malloc(sizeof(struct list_head));
207239
INIT_LIST_HEAD(cache->lists[i]);
@@ -224,12 +256,41 @@ cache_t *cache_create(int size_bits)
224256
for (uint32_t i = 0; i < cache_size; i++) {
225257
INIT_HLIST_HEAD(&cache->map->ht_list_head[i]);
226258
}
259+
cache->lru_capacity = cache_size / 2;
260+
cache_mp =
261+
mpool_create(cache_size * 2 * sizeof(arc_entry_t), sizeof(arc_entry_t));
262+
#else
263+
for (int i = 0; i < THRESHOLD; i++) {
264+
cache->lists[i] = malloc(sizeof(struct list_head));
265+
INIT_LIST_HEAD(cache->lists[i]);
266+
}
227267

268+
cache->map = malloc(sizeof(hashtable_t));
269+
if (!cache->map) {
270+
free(cache->lists);
271+
free(cache);
272+
return NULL;
273+
}
274+
cache->map->ht_list_head = malloc(cache_size * sizeof(struct hlist_head));
275+
if (!cache->map->ht_list_head) {
276+
free(cache->map);
277+
free(cache->lists);
278+
free(cache);
279+
return NULL;
280+
}
281+
for (uint32_t i = 0; i < cache_size; i++) {
282+
INIT_HLIST_HEAD(&cache->map->ht_list_head[i]);
283+
}
284+
cache->list_size = 0;
285+
cache_mp =
286+
mpool_create(cache_size * sizeof(lfu_entry_t), sizeof(lfu_entry_t));
287+
#endif
228288
cache->capacity = cache_size;
229-
cache->lru_capacity = cache_size / 2;
230289
return cache;
231290
}
232291

292+
293+
#if RV32_HAS(ARC)
233294
/* Rules of ARC
234295
* 1. size of LRU_list + size of LFU_list <= c
235296
* 2. size of LRU_list + size of LRU_ghost_list <= c
@@ -273,12 +334,14 @@ static inline void move_to_mru(cache_t *cache,
273334
list_del_init(&entry->list);
274335
list_add(&entry->list, cache->lists[type]);
275336
}
337+
#endif
276338

277339
void *cache_get(cache_t *cache, uint32_t key)
278340
{
279341
if (!cache->capacity || hlist_empty(&cache->map->ht_list_head[HASH(key)]))
280342
return NULL;
281343

344+
#if RV32_HAS(ARC)
282345
arc_entry_t *entry = NULL;
283346
#ifdef __HAVE_TYPEOF
284347
hlist_for_each_entry (entry, &cache->map->ht_list_head[HASH(key)], ht_list)
@@ -323,13 +386,35 @@ void *cache_get(cache_t *cache, uint32_t key)
323386
}
324387

325388
CACHE_ASSERT(cache);
389+
#else
390+
lfu_entry_t *entry = NULL;
391+
#ifdef __HAVE_TYPEOF
392+
hlist_for_each_entry (entry, &cache->map->ht_list_head[HASH(key)], ht_list)
393+
#else
394+
hlist_for_each_entry (entry, &cache->map->ht_list_head[HASH(key)], ht_list,
395+
lfu_entry_t)
396+
#endif
397+
{
398+
if (entry->key == key)
399+
break;
400+
}
401+
if (!entry || entry->key != key)
402+
return NULL;
403+
404+
/* We would translate the block with a frequency of more thanTHRESHOLD */
405+
if (entry->frequency < THRESHOLD) {
406+
list_del_init(&entry->list);
407+
list_add(&entry->list, cache->lists[entry->frequency++]);
408+
}
409+
#endif
326410
/* return NULL if cache miss */
327411
return entry->value;
328412
}
329413

330414
void *cache_put(cache_t *cache, uint32_t key, void *value)
331415
{
332416
void *delete_value = NULL;
417+
#if RV32_HAS(ARC)
333418
assert(cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list] <=
334419
cache->capacity);
335420
/* Before adding new element to cach, we should check the status
@@ -343,7 +428,7 @@ void *cache_put(cache_t *cache, uint32_t key, void *value)
343428
list_del_init(&delete_target->list);
344429
hlist_del_init(&delete_target->ht_list);
345430
delete_value = delete_target->value;
346-
free(delete_target);
431+
mpool_free(cache_mp, delete_target);
347432
cache->list_size[LRU_ghost_list]--;
348433
if (cache->list_size[LRU_list] &&
349434
cache->list_size[LRU_list] >= cache->lru_capacity)
@@ -357,7 +442,7 @@ void *cache_put(cache_t *cache, uint32_t key, void *value)
357442
list_del_init(&delete_target->list);
358443
hlist_del_init(&delete_target->ht_list);
359444
delete_value = delete_target->value;
360-
free(delete_target);
445+
mpool_free(cache_mp, delete_target);
361446
cache->list_size[LRU_list]--;
362447
}
363448
} else {
@@ -372,12 +457,12 @@ void *cache_put(cache_t *cache, uint32_t key, void *value)
372457
list_del_init(&delete_target->list);
373458
hlist_del_init(&delete_target->ht_list);
374459
delete_value = delete_target->value;
375-
free(delete_target);
460+
mpool_free(cache_mp, delete_target);
376461
cache->list_size[LFU_ghost_list]--;
377462
}
378463
REPLACE_LIST(>, >=)
379464
}
380-
arc_entry_t *new_entry = malloc(sizeof(arc_entry_t));
465+
arc_entry_t *new_entry = mpool_alloc(cache_mp);
381466
new_entry->key = key;
382467
new_entry->value = value;
383468
/* check if all cache become LFU */
@@ -393,21 +478,63 @@ void *cache_put(cache_t *cache, uint32_t key, void *value)
393478
hlist_add_head(&new_entry->ht_list, &cache->map->ht_list_head[HASH(key)]);
394479

395480
CACHE_ASSERT(cache);
481+
#else
482+
assert(cache->list_size <= cache->capacity);
483+
/* Before adding new element to cach, we should check the status
484+
* of cache.
485+
*/
486+
if (cache->list_size == cache->capacity) {
487+
for (int i = 0; i < THRESHOLD; i++) {
488+
if (!list_empty(cache->lists[i])) {
489+
lfu_entry_t *delete_target =
490+
list_last_entry(cache->lists[i], lfu_entry_t, list);
491+
list_del_init(&delete_target->list);
492+
hlist_del_init(&delete_target->ht_list);
493+
delete_value = delete_target->value;
494+
cache->list_size--;
495+
mpool_free(cache_mp, delete_target);
496+
break;
497+
}
498+
}
499+
}
500+
lfu_entry_t *new_entry = mpool_alloc(cache_mp);
501+
new_entry->key = key;
502+
new_entry->value = value;
503+
new_entry->frequency = 0;
504+
list_add(&new_entry->list, cache->lists[new_entry->frequency++]);
505+
cache->list_size++;
506+
hlist_add_head(&new_entry->ht_list, &cache->map->ht_list_head[HASH(key)]);
507+
assert(cache->list_size <= cache->capacity);
508+
#endif
396509
return delete_value;
397510
}
398511

399512
void cache_free(cache_t *cache, void (*callback)(void *))
400513
{
514+
#if RV32_HAS(ARC)
401515
for (int i = 0; i < N_CACHE_LIST_TYPES; i++) {
402516
arc_entry_t *entry, *safe;
403517
#ifdef __HAVE_TYPEOF
404518
list_for_each_entry_safe (entry, safe, cache->lists[i], list)
405519
#else
406520
list_for_each_entry_safe (entry, safe, cache->lists[i], list,
407521
arc_entry_t)
522+
#endif
523+
#else
524+
for (int i = 0; i < THRESHOLD; i++) {
525+
if (list_empty(cache->lists[i]))
526+
continue;
527+
lfu_entry_t *entry, *safe;
528+
#ifdef __HAVE_TYPEOF
529+
list_for_each_entry_safe (entry, safe, cache->lists[i], list)
530+
#else
531+
list_for_each_entry_safe (entry, safe, cache->lists[i], list,
532+
lfu_entry_t)
533+
#endif
408534
#endif
409535
callback(entry->value);
410536
}
537+
mpool_destory(cache_mp);
411538
free(cache->map->ht_list_head);
412539
free(cache->map);
413540
free(cache);

src/feature.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@
4848
#define RV32_FEATURE_GDBSTUB 1
4949
#endif
5050

51+
/* Import adaptive replacement cache to manage block */
52+
#ifndef RV32_FEATURE_ARC
53+
#define RV32_FEATURE_ARC 0
54+
#endif
55+
5156
/* Feature test macro */
5257
#define RV32_HAS(x) RV32_FEATURE_##x
5358

tests/cache/arc/cache-get.expect

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
NEW CACHE
2+
NULL
3+
NULL
4+
3
5+
FREE CACHE
File renamed without changes.

0 commit comments

Comments
 (0)