Skip to content

Commit 000a6a5

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 000a6a5

File tree

4 files changed

+458
-0
lines changed

4 files changed

+458
-0
lines changed

.clang-format

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ UseTab: Never
1313
IndentWidth: 4
1414
BreakBeforeBraces: Linux
1515
AccessModifierOffset: -4
16+
ForEachMacros:
17+
- list_for_each_entry
18+
- list_for_each_entry_safe
19+
- hlist_for_each_entry

src/cache.c

Lines changed: 389 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,389 @@
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 <assert.h>
7+
#include <stdbool.h>
8+
#include <stddef.h>
9+
#include <stdlib.h>
10+
#include <string.h>
11+
12+
#include "cache.h"
13+
14+
#define MIN(a, b) ((a < b) ? a : b)
15+
#define GOLDEN_RATIO_32 0x61C88647
16+
#define HASH(val) \
17+
(((val) * (GOLDEN_RATIO_32)) >> (32 - (cache_size_bits))) & (cache_size - 1)
18+
19+
static uint32_t cache_size, cache_size_bits;
20+
21+
/*
22+
* Adaptive Replacement Cache (ARC) improves the fundamental LRU strategy
23+
* by dividing the cache into two lists, T1 and T2. list T1 is for LRU
24+
* strategy and list T2 is for LFU strategy. Moreover, it keeps two ghost
25+
* lists, B1 and B2, with replaced entries from the LRU list going into B1
26+
* and the LFU list going into B2.
27+
*
28+
* Based on B1 and B2, ARC will modify the size of T1 and T2. When a cache
29+
* hit occurs in B1, it indicates that T1's capacity is too little, therefore
30+
* we increase T1's size while decreasing T2. But, if the cache hit occurs in
31+
* B2, we would increase the size of T2 and decrease the size of T1.
32+
*/
33+
typedef enum {
34+
LRU_list,
35+
LFU_list,
36+
LRU_ghost_list,
37+
LFU_ghost_list,
38+
N_CACHE_LIST_TYPES
39+
} cache_list_t;
40+
41+
struct list_head {
42+
struct list_head *prev, *next;
43+
};
44+
45+
struct hlist_head {
46+
struct hlist_node *first;
47+
};
48+
49+
struct hlist_node {
50+
struct hlist_node *next, **pprev;
51+
};
52+
53+
typedef struct {
54+
void *value;
55+
uint32_t key;
56+
cache_list_t type;
57+
struct list_head list;
58+
struct hlist_node ht_list;
59+
} arc_entry_t;
60+
61+
typedef struct {
62+
struct hlist_head *ht_list_head;
63+
} hashtable_t;
64+
65+
typedef struct cache {
66+
struct list_head *lists[N_CACHE_LIST_TYPES];
67+
uint32_t list_size[N_CACHE_LIST_TYPES];
68+
hashtable_t *map;
69+
uint32_t capacity;
70+
uint32_t lru_capacity;
71+
} cache_t;
72+
73+
static inline void INIT_LIST_HEAD(struct list_head *head)
74+
{
75+
head->next = head;
76+
head->prev = head;
77+
}
78+
79+
static inline void list_add(struct list_head *node, struct list_head *head)
80+
{
81+
struct list_head *next = head->next;
82+
83+
next->prev = node;
84+
node->next = next;
85+
node->prev = head;
86+
head->next = node;
87+
}
88+
89+
static inline void list_del(struct list_head *node)
90+
{
91+
struct list_head *next = node->next;
92+
struct list_head *prev = node->prev;
93+
94+
next->prev = prev;
95+
prev->next = next;
96+
}
97+
98+
static inline void list_del_init(struct list_head *node)
99+
{
100+
list_del(node);
101+
INIT_LIST_HEAD(node);
102+
}
103+
104+
#define list_entry(node, type, member) container_of(node, type, member)
105+
106+
#define list_last_entry(head, type, member) \
107+
list_entry((head)->prev, type, member)
108+
109+
#ifdef __HAVE_TYPEOF
110+
#define list_for_each_entry_safe(entry, safe, head, member) \
111+
for (entry = list_entry((head)->next, __typeof__(*entry), member), \
112+
safe = list_entry(entry->member.next, __typeof__(*entry), member); \
113+
&entry->member != (head); entry = safe, \
114+
safe = list_entry(safe->member.next, __typeof__(*entry), member))
115+
#else
116+
#define list_for_each_entry_safe(entry, safe, head, member, type) \
117+
for (entry = list_entry((head)->next, type, member), \
118+
safe = list_entry(entry->member.next, type, member); \
119+
&entry->member != (head); \
120+
entry = safe, safe = list_entry(safe->member.next, type, member))
121+
#endif
122+
123+
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
124+
125+
static inline void INIT_HLIST_NODE(struct hlist_node *h)
126+
{
127+
h->next = NULL;
128+
h->pprev = NULL;
129+
}
130+
131+
static inline int hlist_empty(const struct hlist_head *h)
132+
{
133+
return !h->first;
134+
}
135+
136+
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
137+
{
138+
struct hlist_node *first = h->first;
139+
n->next = first;
140+
if (first)
141+
first->pprev = &n->next;
142+
143+
h->first = n;
144+
n->pprev = &h->first;
145+
}
146+
147+
static inline bool hlist_unhashed(const struct hlist_node *h)
148+
{
149+
return !h->pprev;
150+
}
151+
152+
static inline void hlist_del(struct hlist_node *n)
153+
{
154+
struct hlist_node *next = n->next;
155+
struct hlist_node **pprev = n->pprev;
156+
157+
*pprev = next;
158+
if (next)
159+
next->pprev = pprev;
160+
}
161+
162+
static inline void hlist_del_init(struct hlist_node *n)
163+
{
164+
if (hlist_unhashed(n))
165+
return;
166+
hlist_del(n);
167+
INIT_HLIST_NODE(n);
168+
}
169+
170+
#define hlist_entry(ptr, type, member) container_of(ptr, type, member)
171+
172+
#ifdef __HAVE_TYPEOF
173+
#define hlist_entry_safe(ptr, type, member) \
174+
({ \
175+
typeof(ptr) ____ptr = (ptr); \
176+
____ptr ? hlist_entry(____ptr, type, member) : NULL; \
177+
})
178+
#else
179+
#define hlist_entry_safe(ptr, type, member) \
180+
(ptr) ? hlist_entry(ptr, type, member) : NULL
181+
#endif
182+
183+
#ifdef __HAVE_TYPEOF
184+
#define hlist_for_each_entry(pos, head, member) \
185+
for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member); pos; \
186+
pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
187+
#else
188+
#define hlist_for_each_entry(pos, head, member, type) \
189+
for (pos = hlist_entry_safe((head)->first, type, member); pos; \
190+
pos = hlist_entry_safe((pos)->member.next, type, member))
191+
#endif
192+
193+
cache_t *cache_create(int size_bits)
194+
{
195+
cache_t *cache = malloc(sizeof(cache_t));
196+
if (!cache)
197+
return NULL;
198+
cache_size_bits = size_bits;
199+
cache_size = 1 << size_bits;
200+
201+
for (int i = 0; i < N_CACHE_LIST_TYPES; i++) {
202+
cache->lists[i] = malloc(sizeof(struct list_head));
203+
INIT_LIST_HEAD(cache->lists[i]);
204+
cache->list_size[i] = 0;
205+
}
206+
207+
cache->map = malloc(sizeof(hashtable_t));
208+
if (!cache->map) {
209+
free(cache->lists);
210+
free(cache);
211+
return NULL;
212+
}
213+
cache->map->ht_list_head = malloc(cache_size * sizeof(struct hlist_head));
214+
if (!cache->map->ht_list_head) {
215+
free(cache->map);
216+
free(cache->lists);
217+
free(cache);
218+
return NULL;
219+
}
220+
for (uint32_t i = 0; i < cache_size; i++) {
221+
INIT_HLIST_HEAD(&cache->map->ht_list_head[i]);
222+
}
223+
224+
cache->capacity = cache_size;
225+
cache->lru_capacity = cache_size / 2;
226+
return cache;
227+
}
228+
229+
/* Rules of ARC
230+
* 1. size of LRU_list + size of LFU_list <= c
231+
* 2. size of LRU_list + size of LRU_ghost_list <= c
232+
* 3. size of LFU_list + size of LFU_ghost_list <= 2c
233+
* 4. size of LRU_list + size of LFU_list + size of LRU_ghost_list + size of
234+
* LFU_ghost_list <= 2c
235+
*/
236+
#define CACHE_ASSERT(cache) \
237+
assert(cache->list_size[LRU_list] + cache->list_size[LFU_list] <= \
238+
cache->capacity); \
239+
assert(cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list] <= \
240+
cache->capacity); \
241+
assert(cache->list_size[LFU_list] + cache->list_size[LFU_ghost_list] <= \
242+
2 * cache->capacity); \
243+
assert(cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list] + \
244+
cache->list_size[LFU_list] + \
245+
cache->list_size[LFU_ghost_list] <= \
246+
2 * cache->capacity);
247+
248+
static inline void move_to_mru(cache_t *cache,
249+
arc_entry_t *entry,
250+
const cache_list_t type)
251+
{
252+
cache->list_size[entry->type]--;
253+
cache->list_size[type]++;
254+
entry->type = type;
255+
list_del_init(&entry->list);
256+
list_add(&entry->list, cache->lists[type]);
257+
}
258+
259+
static inline void replace_list(cache_t *cache)
260+
{
261+
if (cache->list_size[LRU_list] >= cache->lru_capacity)
262+
move_to_mru(cache,
263+
list_last_entry(cache->lists[LRU_list], arc_entry_t, list),
264+
LRU_ghost_list);
265+
else if (cache->list_size[LFU_list] >=
266+
(cache->capacity - cache->lru_capacity))
267+
move_to_mru(cache,
268+
list_last_entry(cache->lists[LFU_list], arc_entry_t, list),
269+
LFU_ghost_list);
270+
}
271+
272+
void *cache_get(cache_t *cache, uint32_t key)
273+
{
274+
if (!cache->capacity || hlist_empty(&cache->map->ht_list_head[HASH(key)]))
275+
return NULL;
276+
277+
arc_entry_t *entry = NULL;
278+
#ifdef __HAVE_TYPEOF
279+
hlist_for_each_entry (entry, &cache->map->ht_list_head[HASH(key)], ht_list)
280+
#else
281+
hlist_for_each_entry (entry, &cache->map->ht_list_head[HASH(key)], ht_list,
282+
arc_entry_t)
283+
#endif
284+
{
285+
if (entry->key == key)
286+
break;
287+
}
288+
if (!entry || entry->key != key)
289+
return NULL;
290+
/* cache hit in LRU_list */
291+
if (entry->type == LRU_list) {
292+
replace_list(cache);
293+
move_to_mru(cache, entry, LFU_list);
294+
}
295+
296+
/* cache hit in LFU_list */
297+
if (entry->type == LFU_list)
298+
move_to_mru(cache, entry, LFU_list);
299+
300+
/* cache hit in LRU_ghost_list */
301+
if (entry->type == LRU_ghost_list) {
302+
cache->lru_capacity = MIN(cache->lru_capacity + 1, cache->capacity);
303+
replace_list(cache);
304+
move_to_mru(cache, entry, LFU_list);
305+
}
306+
307+
/* cache hit in LFU_ghost_list */
308+
if (entry->type == LFU_ghost_list) {
309+
cache->lru_capacity = cache->lru_capacity ? cache->lru_capacity - 1 : 0;
310+
replace_list(cache);
311+
move_to_mru(cache, entry, LFU_list);
312+
}
313+
CACHE_ASSERT(cache);
314+
/* return NULL if cache miss */
315+
return entry->value;
316+
}
317+
318+
void *cache_put(cache_t *cache, uint32_t key, void *value)
319+
{
320+
void *delete_value = NULL;
321+
assert(cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list] <=
322+
cache->capacity);
323+
/* Before adding new element to cach, we should check the status
324+
* of cache.
325+
*/
326+
if ((cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list]) ==
327+
cache->capacity) {
328+
if (cache->list_size[LRU_list] < cache->capacity) {
329+
arc_entry_t *delete_target = list_last_entry(
330+
cache->lists[LRU_ghost_list], arc_entry_t, list);
331+
list_del_init(&delete_target->list);
332+
hlist_del_init(&delete_target->ht_list);
333+
delete_value = delete_target->value;
334+
free(delete_target);
335+
cache->list_size[LRU_ghost_list]--;
336+
replace_list(cache);
337+
} else {
338+
arc_entry_t *delete_target =
339+
list_last_entry(cache->lists[LRU_list], arc_entry_t, list);
340+
list_del_init(&delete_target->list);
341+
hlist_del_init(&delete_target->ht_list);
342+
delete_value = delete_target->value;
343+
free(delete_target);
344+
cache->list_size[LRU_list]--;
345+
}
346+
} else {
347+
assert(cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list] <
348+
cache->capacity);
349+
uint32_t size =
350+
cache->list_size[LRU_list] + cache->list_size[LRU_ghost_list] +
351+
cache->list_size[LFU_list] + cache->list_size[LFU_ghost_list];
352+
if (size == cache->capacity * 2) {
353+
arc_entry_t *delete_target = list_last_entry(
354+
cache->lists[LFU_ghost_list], arc_entry_t, list);
355+
list_del_init(&delete_target->list);
356+
hlist_del_init(&delete_target->ht_list);
357+
delete_value = delete_target->value;
358+
free(delete_target);
359+
cache->list_size[LFU_ghost_list]--;
360+
}
361+
replace_list(cache);
362+
}
363+
arc_entry_t *new_entry = malloc(sizeof(arc_entry_t));
364+
new_entry->key = key;
365+
new_entry->value = value;
366+
new_entry->type = LRU_list;
367+
list_add(&new_entry->list, cache->lists[LRU_list]);
368+
hlist_add_head(&new_entry->ht_list, &cache->map->ht_list_head[HASH(key)]);
369+
cache->list_size[LRU_list]++;
370+
CACHE_ASSERT(cache);
371+
return delete_value;
372+
}
373+
374+
void cache_free(cache_t *cache, void (*callback)(void *))
375+
{
376+
for (int i = 0; i < N_CACHE_LIST_TYPES; i++) {
377+
arc_entry_t *entry, *safe;
378+
#ifdef __HAVE_TYPEOF
379+
list_for_each_entry_safe (entry, safe, cache->lists[i], list)
380+
#else
381+
list_for_each_entry_safe (entry, safe, cache->lists[i], list,
382+
arc_entry_t)
383+
#endif
384+
callback(entry->value);
385+
}
386+
free(cache->map->ht_list_head);
387+
free(cache->map);
388+
free(cache);
389+
}

0 commit comments

Comments
 (0)