Skip to content

Commit de63788

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 de63788

File tree

4 files changed

+456
-0
lines changed

4 files changed

+456
-0
lines changed

.clang-format

+4
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

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

0 commit comments

Comments
 (0)