-
Notifications
You must be signed in to change notification settings - Fork 107
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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%.
- Loading branch information
Showing
8 changed files
with
776 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
#include "cache.h" | ||
|
||
#define min(a, b) ((a < b) ? a : b) | ||
#define max(a, b) ((a > b) ? a : b) | ||
#define BITS 10 | ||
#define SIZE 1024 | ||
#define GOLDEN_RATIO_32 0x61C88647 | ||
#define HASH(val) (((val) *GOLDEN_RATIO_32) >> (32 - BITS)) % SIZE | ||
|
||
typedef struct arc_entry { | ||
void *value; | ||
uint32_t key; | ||
arc_type_t arc_type; | ||
struct list_head list; | ||
struct list_head ht_list; | ||
} arc_entry_t; | ||
|
||
typedef struct hashtable { | ||
struct list_head *ht_list_head; | ||
} hashtable_t; | ||
|
||
cache_t *cache_create() | ||
{ | ||
cache_t *cache = (cache_t *) malloc(sizeof(cache_t)); | ||
for (int i = 0; i < 4; i++) { | ||
cache->list_table[i] = | ||
(struct list_head *) malloc(sizeof(struct list_head)); | ||
INIT_LIST_HEAD(cache->list_table[i]); | ||
cache->list_size[i] = 0; | ||
} | ||
cache->map = (hashtable_t *) malloc(sizeof(hashtable_t)); | ||
cache->map->ht_list_head = | ||
(struct list_head *) malloc(SIZE * sizeof(struct list_head)); | ||
|
||
for (int i = 0; i < SIZE; i++) { | ||
INIT_LIST_HEAD(&cache->map->ht_list_head[i]); | ||
} | ||
|
||
cache->c = SIZE; | ||
cache->p = SIZE / 2; | ||
#if RV32_HAS(ARCACHE_INFO) | ||
cache->get_time = 0; | ||
cache->hit_time = 0; | ||
#endif | ||
return cache; | ||
} | ||
|
||
void cache_free(cache_t *cache, void (*release_entry)(void *)) | ||
{ | ||
for (int i = 0; i < 4; i++) { | ||
arc_entry_t *entry, *safe; | ||
list_for_each_entry_safe(entry, safe, cache->list_table[i], list) | ||
release_entry(entry->value); | ||
free(cache->list_table[i]); | ||
} | ||
free(cache->map->ht_list_head); | ||
free(cache->map); | ||
free(cache); | ||
} | ||
|
||
/* Rule of ARC | ||
* 1. size of T1 + size of T2 <= c | ||
* 2. size of T1 + size of B1 <= c | ||
* 3. size of T2 + size of B2 <= 2c | ||
* 4. size of T1 + size of T2 + size of B1 + size of B2 <= 2c | ||
*/ | ||
#if RV32_HAS(ARCACHE_INFO) | ||
void assert_cache(cache_t *cache) | ||
{ | ||
assert(cache->list_size[T1] + cache->list_size[T2] <= cache->c); | ||
assert(cache->list_size[T1] + cache->list_size[B1] <= cache->c); | ||
assert(cache->list_size[T2] + cache->list_size[B2] <= 2 * cache->c); | ||
assert(cache->list_size[T1] + cache->list_size[B1] + cache->list_size[T2] + | ||
cache->list_size[B2] <= | ||
2 * cache->c); | ||
} | ||
#endif | ||
|
||
void move_to_mru(cache_t *cache, arc_entry_t *entry, const arc_type_t arc_type) | ||
{ | ||
cache->list_size[entry->arc_type]--; | ||
cache->list_size[arc_type]++; | ||
entry->arc_type = arc_type; | ||
list_move(&entry->list, cache->list_table[arc_type]); | ||
} | ||
|
||
void replaceT1(cache_t *cache) | ||
{ | ||
if (cache->list_size[T1] >= cache->p) | ||
move_to_mru(cache, | ||
list_last_entry(cache->list_table[T1], arc_entry_t, list), | ||
B1); | ||
} | ||
void replaceT2(cache_t *cache) | ||
{ | ||
if (cache->list_size[T2] >= (cache->c - cache->p)) | ||
move_to_mru(cache, | ||
list_last_entry(cache->list_table[T2], arc_entry_t, list), | ||
B2); | ||
} | ||
|
||
void *cache_get(cache_t *cache, uint32_t key) | ||
{ | ||
if (cache->c <= 0 || list_empty(&cache->map->ht_list_head[HASH(key)])) | ||
return NULL; | ||
|
||
arc_entry_t *entry = NULL; | ||
list_for_each_entry(entry, &cache->map->ht_list_head[HASH(key)], ht_list) | ||
{ | ||
if (entry->key == key) | ||
break; | ||
} | ||
#if RV32_HAS(ARCACHE_INFO) | ||
cache->get_time++; | ||
#endif | ||
if (!entry || entry->key != key) | ||
return NULL; | ||
/* cache hit in T1 */ | ||
if (entry->arc_type == T1) { | ||
#if RV32_HAS(ARCACHE_INFO) | ||
cache->hit_time++; | ||
#endif | ||
replaceT2(cache); | ||
move_to_mru(cache, entry, T2); | ||
} | ||
|
||
/* cache hit in T2 */ | ||
if (entry->arc_type == T2) { | ||
#if RV32_HAS(ARCACHE_INFO) | ||
cache->hit_time++; | ||
#endif | ||
move_to_mru(cache, entry, T2); | ||
} | ||
|
||
/* cache hit in B1 */ | ||
if (entry->arc_type == B1) { | ||
cache->p = min(cache->p + 1, cache->c); | ||
replaceT2(cache); | ||
move_to_mru(cache, entry, T2); | ||
} | ||
|
||
/* cache hit in B2 */ | ||
if (entry->arc_type == B2) { | ||
cache->p = max(cache->p - 1, 0); | ||
replaceT1(cache); | ||
move_to_mru(cache, entry, T2); | ||
} | ||
#if RV32_HAS(ARCACHE_INFO) | ||
assert_cache(cache); | ||
#endif | ||
/* return NULL if cache miss */ | ||
return entry->value; | ||
} | ||
|
||
void *cache_put(cache_t *cache, uint32_t key, void *value) | ||
{ | ||
#if RV32_HAS(ARCACHE_INFO) | ||
cache->get_time++; | ||
#endif | ||
void *delete_value = NULL; | ||
#if RV32_HAS(ARCACHE_INFO) | ||
assert(cache->list_size[T1] + cache->list_size[B1] <= cache->c); | ||
#endif | ||
/* Before adding new element to cach, we should check the status | ||
* of cache. | ||
*/ | ||
if ((cache->list_size[T1] + cache->list_size[B1]) == cache->c) { | ||
if (cache->list_size[T1] < cache->c) { | ||
arc_entry_t *delete_target = | ||
list_last_entry(cache->list_table[B1], arc_entry_t, list); | ||
list_del_init(&delete_target->list); | ||
list_del_init(&delete_target->ht_list); | ||
delete_value = delete_target->value; | ||
free(delete_target); | ||
cache->list_size[B1]--; | ||
replaceT1(cache); | ||
} else { | ||
arc_entry_t *delete_target = | ||
list_last_entry(cache->list_table[T1], arc_entry_t, list); | ||
list_del_init(&delete_target->list); | ||
list_del_init(&delete_target->ht_list); | ||
delete_value = delete_target->value; | ||
free(delete_target); | ||
cache->list_size[T1]--; | ||
} | ||
} else { | ||
#if RV32_HAS(ARCACHE_INFO) | ||
assert(cache->list_size[T1] + cache->list_size[B1] < cache->c); | ||
#endif | ||
uint32_t size = cache->list_size[T1] + cache->list_size[B1] + | ||
cache->list_size[T2] + cache->list_size[B2]; | ||
if (size == cache->c * 2) { | ||
arc_entry_t *delete_target = | ||
list_last_entry(cache->list_table[B2], arc_entry_t, list); | ||
list_del_init(&delete_target->list); | ||
list_del_init(&delete_target->ht_list); | ||
delete_value = delete_target->value; | ||
free(delete_target); | ||
cache->list_size[B2]--; | ||
} | ||
if (cache->list_size[T1] + cache->list_size[T2] >= cache->c && | ||
cache->list_size[T1] < cache->p) | ||
replaceT2(cache); | ||
else | ||
replaceT1(cache); | ||
} | ||
arc_entry_t *new_entry = (arc_entry_t *) malloc(sizeof(arc_entry_t)); | ||
new_entry->key = key; | ||
new_entry->value = value; | ||
new_entry->arc_type = T1; | ||
list_add(&new_entry->list, cache->list_table[T1]); | ||
list_add(&new_entry->ht_list, &cache->map->ht_list_head[HASH(key)]); | ||
cache->list_size[T1]++; | ||
#if RV32_HAS(ARCACHE_INFO) | ||
assert_cache(cache); | ||
#endif | ||
return delete_value; | ||
} | ||
|
||
#if RV32_HAS(ARCACHE_INFO) | ||
void cache_print_stats(cache_t *cache) | ||
{ | ||
printf( | ||
"requests: %12lu \n" | ||
"hits: %12lu \n" | ||
"ratio: %lf%%\n", | ||
cache->get_time, cache->hit_time, | ||
cache->hit_time * 100 / (double) cache->get_time); | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#include <assert.h> | ||
#include <stdbool.h> | ||
#include <stdint.h> | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
|
||
#include "list.h" | ||
|
||
/* | ||
T1: LRU List | ||
T2: LFU List | ||
B1: LRU Ghost List | ||
B2: LFU Ghost List | ||
*/ | ||
typedef enum { T1, B1, T2, B2 } arc_type_t; | ||
|
||
struct hashtable; | ||
|
||
typedef struct cache { | ||
struct list_head *list_table[4]; | ||
uint32_t list_size[4]; | ||
struct hashtable *map; | ||
uint32_t c; | ||
uint32_t p; | ||
#if RV32_HAS(ARCACHE_INFO) | ||
uint64_t get_time; | ||
uint64_t hit_time; | ||
#endif | ||
} cache_t; | ||
|
||
cache_t *cache_create(); | ||
|
||
void cache_free(cache_t *cache, void (*release_entry)(void *)); | ||
|
||
void *cache_get(cache_t *cache, uint32_t key); | ||
|
||
void *cache_put(cache_t *cache, uint32_t key, void *value); | ||
|
||
#if RV32_HAS(ARCACHE_INFO) | ||
void cache_print_stats(cache_t *cache); | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.