Skip to content

Commit 88fa550

Browse files
committed
Add adaptive replacement cache
Current basic block management consumes a significant amount of memory, which leads to unnecessary waste due to frequent map allocation and release. Adaptive Replacement Cache (ARC) is a page replacement algorithm with better performance than least recently used (LRU). After the translated blocks are handled by ARC, better memory usage and hit rates can be achieved by keeping track of frequently used and recently used pages, as well as a recent eviction history for both. According to the cache information obtained while running CoreMark, the cache hit rate of ARC can reach over 99%.
1 parent a713b4c commit 88fa550

File tree

8 files changed

+780
-9
lines changed

8 files changed

+780
-9
lines changed

Makefile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ include mk/toolchain.mk
44
OUT ?= build
55
BIN := $(OUT)/rv32emu
66

7-
CFLAGS = -std=gnu99 -O2 -Wall -Wextra
7+
CFLAGS = -std=gnu99 -O2 -Wall -Wextra -lpthread
88
CFLAGS += -Wno-unused-label
99
CFLAGS += -include src/common.h
1010

@@ -55,7 +55,7 @@ $(OUT)/syscall_sdl.o: CFLAGS += $(shell sdl2-config --cflags)
5555
LDFLAGS += $(shell sdl2-config --libs)
5656
endif
5757

58-
ENABLE_GDBSTUB ?= 1
58+
ENABLE_GDBSTUB ?= 0
5959
$(call set-feature, GDBSTUB)
6060
ifeq ($(call has, GDBSTUB), 1)
6161
GDBSTUB_OUT = $(abspath $(OUT)/mini-gdbstub)
@@ -74,6 +74,10 @@ gdbstub-test: $(BIN)
7474
$(Q)tests/gdbstub.sh && $(call notice, [OK])
7575
endif
7676

77+
# Import adaptive replacement cache
78+
ENABLE_ARCACHE ?= 1
79+
$(call set-feature, ARCACHE)
80+
7781
# For tail-call elimination, we need a specific set of build flags applied.
7882
# FIXME: On macOS + Apple Silicon, -fno-stack-protector might have a negative impact.
7983
$(OUT)/emulate.o: CFLAGS += -fomit-frame-pointer -fno-stack-check -fno-stack-protector
@@ -93,6 +97,7 @@ OBJS := \
9397
emulate.o \
9498
riscv.o \
9599
elf.o \
100+
cache.o \
96101
$(OBJS_EXT) \
97102
main.o
98103

src/cache.c

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/*
2+
* rv32emu is freely redistributable under the MIT License. See the file
3+
* "LICENSE" for information on usage and redistribution of this file.
4+
*/
5+
6+
#include "cache.h"
7+
8+
#define MIN(a, b) ((a < b) ? a : b)
9+
#define BITS 10
10+
#define SIZE 1024
11+
#define GOLDEN_RATIO_32 0x61C88647
12+
#define HASH(val) (((val) *GOLDEN_RATIO_32) >> (32 - BITS)) % SIZE
13+
14+
typedef struct arc_entry {
15+
void *value;
16+
uint32_t key;
17+
list_type_t arc_type;
18+
struct list_head list;
19+
struct list_head ht_list;
20+
} arc_entry_t;
21+
22+
typedef struct hashtable {
23+
struct list_head *ht_list_head;
24+
} hashtable_t;
25+
26+
cache_t *cache_create()
27+
{
28+
cache_t *cache = (cache_t *) malloc(sizeof(cache_t));
29+
for (int i = 0; i < 4; i++) {
30+
cache->list_table[i] =
31+
(struct list_head *) malloc(sizeof(struct list_head));
32+
INIT_LIST_HEAD(cache->list_table[i]);
33+
cache->list_size[i] = 0;
34+
}
35+
cache->map = (hashtable_t *) malloc(sizeof(hashtable_t));
36+
cache->map->ht_list_head =
37+
(struct list_head *) malloc(SIZE * sizeof(struct list_head));
38+
39+
for (int i = 0; i < SIZE; i++) {
40+
INIT_LIST_HEAD(&cache->map->ht_list_head[i]);
41+
}
42+
43+
cache->capacity = SIZE;
44+
cache->lru_capacity = SIZE / 2;
45+
#if CACHE_INFO
46+
cache->get_time = 0;
47+
cache->hit_time = 0;
48+
#endif
49+
return cache;
50+
}
51+
52+
/* Rule of ARC
53+
* 1. size of LRU_list + size of LFU_list <= c
54+
* 2. size of LRU_list + size of LRU_ghost_list <= c
55+
* 3. size of LFU_list + size of LFU_ghost_list <= 2c
56+
* 4. size of LRU_list + size of LFU_list + size of LRU_ghost_list + size of
57+
* LFU_ghost_list <= 2c
58+
*/
59+
void assert_cache(cache_t *cache)
60+
{
61+
assert(cache->list_size[LRU_list] + cache->list_size[LFU_list] <=
62+
cache->capacity);
63+
assert(cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list] <=
64+
cache->capacity);
65+
assert(cache->list_size[LFU_list] + cache->list_size[LFU_ghost_list] <=
66+
2 * cache->capacity);
67+
assert(cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list] +
68+
cache->list_size[LFU_list] + cache->list_size[LFU_ghost_list] <=
69+
2 * cache->capacity);
70+
}
71+
72+
void move_to_mru(cache_t *cache, arc_entry_t *entry, const list_type_t arc_type)
73+
{
74+
cache->list_size[entry->arc_type]--;
75+
cache->list_size[arc_type]++;
76+
entry->arc_type = arc_type;
77+
list_move(&entry->list, cache->list_table[arc_type]);
78+
}
79+
80+
void replace_LRU_list(cache_t *cache)
81+
{
82+
if (cache->list_size[LRU_list] >= cache->lru_capacity)
83+
move_to_mru(
84+
cache,
85+
list_last_entry(cache->list_table[LRU_list], arc_entry_t, list),
86+
LRU_ghost_list);
87+
}
88+
void replace_LFU_list(cache_t *cache)
89+
{
90+
if (cache->list_size[LFU_list] >= (cache->capacity - cache->lru_capacity))
91+
move_to_mru(
92+
cache,
93+
list_last_entry(cache->list_table[LFU_list], arc_entry_t, list),
94+
LFU_ghost_list);
95+
}
96+
97+
void *cache_get(cache_t *cache, uint32_t key)
98+
{
99+
if (cache->capacity <= 0 ||
100+
list_empty(&cache->map->ht_list_head[HASH(key)]))
101+
return NULL;
102+
103+
arc_entry_t *entry = NULL;
104+
list_for_each_entry(entry, &cache->map->ht_list_head[HASH(key)], ht_list)
105+
{
106+
if (entry->key == key)
107+
break;
108+
}
109+
#if CACHE_INFO
110+
cache->get_time++;
111+
#endif
112+
if (!entry || entry->key != key)
113+
return NULL;
114+
/* cache hit in LRU_list */
115+
if (entry->arc_type == LRU_list) {
116+
#if CACHE_INFO
117+
cache->hit_time++;
118+
#endif
119+
replace_LFU_list(cache);
120+
move_to_mru(cache, entry, LFU_list);
121+
}
122+
123+
/* cache hit in LFU_list */
124+
if (entry->arc_type == LFU_list) {
125+
#if CACHE_INFO
126+
cache->hit_time++;
127+
#endif
128+
move_to_mru(cache, entry, LFU_list);
129+
}
130+
131+
/* cache hit in LRU_ghost_list */
132+
if (entry->arc_type == LRU_ghost_list) {
133+
cache->lru_capacity = MIN(cache->lru_capacity + 1, cache->capacity);
134+
replace_LFU_list(cache);
135+
move_to_mru(cache, entry, LFU_list);
136+
}
137+
138+
/* cache hit in LFU_ghost_list */
139+
if (entry->arc_type == LFU_ghost_list) {
140+
cache->lru_capacity = cache->lru_capacity ? cache->lru_capacity - 1 : 0;
141+
replace_LRU_list(cache);
142+
move_to_mru(cache, entry, LFU_list);
143+
}
144+
#if CACHE_INFO
145+
assert_cache(cache);
146+
#endif
147+
/* return NULL if cache miss */
148+
return entry->value;
149+
}
150+
151+
void *cache_put(cache_t *cache, uint32_t key, void *value)
152+
{
153+
#if CACHE_INFO
154+
cache->get_time++;
155+
#endif
156+
void *delete_value = NULL;
157+
assert(cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list] <=
158+
cache->capacity);
159+
/* Before adding new element to cach, we should check the status
160+
* of cache.
161+
*/
162+
if ((cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list]) ==
163+
cache->capacity) {
164+
if (cache->list_size[LRU_list] < cache->capacity) {
165+
arc_entry_t *delete_target = list_last_entry(
166+
cache->list_table[LRU_ghost_list], arc_entry_t, list);
167+
list_del_init(&delete_target->list);
168+
list_del_init(&delete_target->ht_list);
169+
delete_value = delete_target->value;
170+
free(delete_target);
171+
cache->list_size[LRU_ghost_list]--;
172+
replace_LRU_list(cache);
173+
} else {
174+
arc_entry_t *delete_target =
175+
list_last_entry(cache->list_table[LRU_list], arc_entry_t, list);
176+
list_del_init(&delete_target->list);
177+
list_del_init(&delete_target->ht_list);
178+
delete_value = delete_target->value;
179+
free(delete_target);
180+
cache->list_size[LRU_list]--;
181+
}
182+
} else {
183+
assert(cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list] <
184+
cache->capacity);
185+
uint32_t size =
186+
cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list] +
187+
cache->list_size[LFU_list] + cache->list_size[LFU_ghost_list];
188+
if (size == cache->capacity * 2) {
189+
arc_entry_t *delete_target = list_last_entry(
190+
cache->list_table[LFU_ghost_list], arc_entry_t, list);
191+
list_del_init(&delete_target->list);
192+
list_del_init(&delete_target->ht_list);
193+
delete_value = delete_target->value;
194+
free(delete_target);
195+
cache->list_size[LFU_ghost_list]--;
196+
}
197+
if (cache->list_size[LRU_list] + cache->list_size[LFU_list] >=
198+
cache->capacity &&
199+
cache->list_size[LRU_list] < cache->lru_capacity)
200+
replace_LFU_list(cache);
201+
else
202+
replace_LRU_list(cache);
203+
}
204+
arc_entry_t *new_entry = (arc_entry_t *) malloc(sizeof(arc_entry_t));
205+
new_entry->key = key;
206+
new_entry->value = value;
207+
new_entry->arc_type = LRU_list;
208+
list_add(&new_entry->list, cache->list_table[LRU_list]);
209+
list_add(&new_entry->ht_list, &cache->map->ht_list_head[HASH(key)]);
210+
cache->list_size[LRU_list]++;
211+
assert_cache(cache);
212+
return delete_value;
213+
}
214+
215+
#if CACHE_INFO
216+
void cache_print_stats(cache_t *cache)
217+
{
218+
printf(
219+
"requests: %12lu \n"
220+
"hits: %12lu \n"
221+
"ratio: %lf%%\n",
222+
cache->get_time, cache->hit_time,
223+
cache->hit_time * 100 / (double) cache->get_time);
224+
}
225+
#endif
226+
227+
void cache_free(cache_t *cache, void (*release_entry)(void *))
228+
{
229+
#if CACHE_INFO
230+
cache_print_stats(cache);
231+
#endif
232+
for (int i = 0; i < 4; i++) {
233+
arc_entry_t *entry, *safe;
234+
list_for_each_entry_safe(entry, safe, cache->list_table[i], list)
235+
release_entry(entry->value);
236+
free(cache->list_table[i]);
237+
}
238+
free(cache->map->ht_list_head);
239+
free(cache->map);
240+
free(cache);
241+
}

src/cache.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* rv32emu is freely redistributable under the MIT License. See the file
3+
* "LICENSE" for information on usage and redistribution of this file.
4+
*/
5+
6+
#pragma once
7+
8+
#include <assert.h>
9+
#include <stdbool.h>
10+
#include <stdint.h>
11+
#include <stdio.h>
12+
#include <stdlib.h>
13+
#include <string.h>
14+
15+
#include "list.h"
16+
#define CACHE_INFO 0
17+
18+
typedef enum { LRU_list, LFU_list, LRU_ghost_list, LFU_ghost_list } list_type_t;
19+
20+
struct hashtable;
21+
22+
typedef struct cache {
23+
struct list_head *list_table[4];
24+
uint32_t list_size[4];
25+
struct hashtable *map;
26+
uint32_t capacity;
27+
uint32_t lru_capacity;
28+
#if CACHE_INFO
29+
uint64_t get_time;
30+
uint64_t hit_time;
31+
#endif
32+
} cache_t;
33+
34+
cache_t *cache_create();
35+
36+
void cache_free(cache_t *cache, void (*release_entry)(void *));
37+
38+
void *cache_get(cache_t *cache, uint32_t key);
39+
40+
void *cache_put(cache_t *cache, uint32_t key, void *value);
41+
42+
#if CACHE_INFO
43+
void cache_print_stats(cache_t *cache);
44+
#endif

0 commit comments

Comments
 (0)