use a separate rw mutex for each sheet cache bucket
This commit is contained in:
parent
155226b60e
commit
878c6a09a5
255
src/sheet.c
255
src/sheet.c
@ -80,11 +80,16 @@ struct cache_node {
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
struct cache {
|
struct cache_bucket {
|
||||||
struct sys_rw_mutex rw_mutex;
|
struct sys_rw_mutex rw_mutex;
|
||||||
|
struct cache_node *first;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cache {
|
||||||
struct arena arena;
|
struct arena arena;
|
||||||
struct cache_node **buckets;
|
struct cache_bucket *buckets;
|
||||||
struct cache_node *first_free;
|
struct sys_mutex node_pool_mutex;
|
||||||
|
struct cache_node *node_pool_first_free;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sheet_scope_reference {
|
struct sheet_scope_reference {
|
||||||
@ -166,9 +171,12 @@ struct sheet_startup_receipt sheet_startup(struct work_startup_receipt *work_sr,
|
|||||||
(UNUSED)asset_cache_sr;
|
(UNUSED)asset_cache_sr;
|
||||||
(UNUSED)resource_sr;
|
(UNUSED)resource_sr;
|
||||||
|
|
||||||
G.cache.rw_mutex = sys_rw_mutex_alloc();
|
G.cache.node_pool_mutex = sys_mutex_alloc();
|
||||||
G.cache.arena = arena_alloc(GIGABYTE(64));
|
G.cache.arena = arena_alloc(GIGABYTE(64));
|
||||||
G.cache.buckets = arena_push_array_zero(&G.cache.arena, struct cache_node *, CACHE_BUCKETS_COUNT);
|
G.cache.buckets = arena_push_array_zero(&G.cache.arena, struct cache_bucket, CACHE_BUCKETS_COUNT);
|
||||||
|
for (u64 i = 0; i < CACHE_BUCKETS_COUNT; ++i) {
|
||||||
|
G.cache.buckets[i].rw_mutex = sys_rw_mutex_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
G.loader_cmd_arena = arena_alloc(GIGABYTE(64));
|
G.loader_cmd_arena = arena_alloc(GIGABYTE(64));
|
||||||
G.loaders_mutex = sys_mutex_alloc();
|
G.loaders_mutex = sys_mutex_alloc();
|
||||||
@ -335,7 +343,6 @@ INTERNAL void sheet_load(struct cache_node *n, struct sheet_tag tag)
|
|||||||
INTERNAL void scope_ensure_reference(struct sheet_scope *scope, struct cache_node *cache_node, u64 cache_bucket_index)
|
INTERNAL void scope_ensure_reference(struct sheet_scope *scope, struct cache_node *cache_node, u64 cache_bucket_index)
|
||||||
{
|
{
|
||||||
__prof;
|
__prof;
|
||||||
|
|
||||||
struct sheet_scope_reference **ref_next = &scope->reference_buckets[cache_bucket_index];
|
struct sheet_scope_reference **ref_next = &scope->reference_buckets[cache_bucket_index];
|
||||||
struct sheet_scope_reference *ref = *ref_next;
|
struct sheet_scope_reference *ref = *ref_next;
|
||||||
while (ref) {
|
while (ref) {
|
||||||
@ -416,12 +423,13 @@ INTERNAL struct cache_node *node_lookup_touch(struct sheet_scope *scope, struct
|
|||||||
struct cache_node **nonmatching_next = NULL;
|
struct cache_node **nonmatching_next = NULL;
|
||||||
|
|
||||||
u64 cache_bucket_index = tag.hash % CACHE_BUCKETS_COUNT;
|
u64 cache_bucket_index = tag.hash % CACHE_BUCKETS_COUNT;
|
||||||
|
struct cache_bucket *bucket = &G.cache.buckets[cache_bucket_index];
|
||||||
|
|
||||||
/* Lookup */
|
/* Lookup */
|
||||||
/* TODO: Spinlock */
|
/* TODO: Spinlock */
|
||||||
sys_rw_mutex_lock_shared(&G.cache.rw_mutex);
|
sys_rw_mutex_lock_shared(&bucket->rw_mutex);
|
||||||
{
|
{
|
||||||
nonmatching_next = &G.cache.buckets[cache_bucket_index];
|
nonmatching_next = &bucket->first;
|
||||||
n = *nonmatching_next;
|
n = *nonmatching_next;
|
||||||
while (n) {
|
while (n) {
|
||||||
if (n->hash == tag.hash) {
|
if (n->hash == tag.hash) {
|
||||||
@ -434,20 +442,26 @@ INTERNAL struct cache_node *node_lookup_touch(struct sheet_scope *scope, struct
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sys_rw_mutex_unlock_shared(&G.cache.rw_mutex);
|
sys_rw_mutex_unlock_shared(&bucket->rw_mutex);
|
||||||
|
|
||||||
/* Allocate new node if necessary */
|
/* Allocate new node if necessary */
|
||||||
if (!n) {
|
if (!n) {
|
||||||
__profscope(node_lookup_allocate);
|
__profscope(node_lookup_allocate);
|
||||||
sys_rw_mutex_lock_exclusive(&G.cache.rw_mutex);
|
sys_rw_mutex_lock_exclusive(&bucket->rw_mutex);
|
||||||
{
|
{
|
||||||
if (G.cache.first_free) {
|
/* Alloc node */
|
||||||
n = G.cache.first_free;
|
sys_mutex_lock(&G.cache.node_pool_mutex);
|
||||||
G.cache.first_free = n->next_free;
|
{
|
||||||
MEMZERO_STRUCT(n);
|
if (G.cache.node_pool_first_free) {
|
||||||
} else {
|
n = G.cache.node_pool_first_free;
|
||||||
n = arena_push_zero(&G.cache.arena, struct cache_node);
|
G.cache.node_pool_first_free = n->next_free;
|
||||||
|
MEMZERO_STRUCT(n);
|
||||||
|
} else {
|
||||||
|
n = arena_push_zero(&G.cache.arena, struct cache_node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
sys_mutex_unlock(&G.cache.node_pool_mutex);
|
||||||
|
/* Init node and add to bucket */
|
||||||
scope_ensure_reference(scope, n, cache_bucket_index);
|
scope_ensure_reference(scope, n, cache_bucket_index);
|
||||||
*nonmatching_next = n;
|
*nonmatching_next = n;
|
||||||
if (nonmatching) {
|
if (nonmatching) {
|
||||||
@ -456,7 +470,7 @@ INTERNAL struct cache_node *node_lookup_touch(struct sheet_scope *scope, struct
|
|||||||
}
|
}
|
||||||
n->hash = tag.hash;
|
n->hash = tag.hash;
|
||||||
}
|
}
|
||||||
sys_rw_mutex_unlock_exclusive(&G.cache.rw_mutex);
|
sys_rw_mutex_unlock_exclusive(&bucket->rw_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
@ -574,7 +588,13 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_loader_thread_entry_point, arg)
|
|||||||
break;
|
break;
|
||||||
} else if (!G.first_loader_cmd) {
|
} else if (!G.first_loader_cmd) {
|
||||||
/* Wait for work */
|
/* Wait for work */
|
||||||
|
#if 1
|
||||||
sys_condition_variable_wait(&G.loaders_cv, &G.loaders_mutex);
|
sys_condition_variable_wait(&G.loaders_cv, &G.loaders_mutex);
|
||||||
|
#else
|
||||||
|
sys_mutex_unlock(&G.loaders_mutex);
|
||||||
|
sys_sleep(0.5);
|
||||||
|
sys_mutex_lock(&G.loaders_mutex);
|
||||||
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -611,14 +631,22 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_evictor_thread_entry_point, arg)
|
|||||||
(UNUSED)arg;
|
(UNUSED)arg;
|
||||||
|
|
||||||
struct evict_node {
|
struct evict_node {
|
||||||
|
b32 evicted;
|
||||||
struct cache_node *cache_node;
|
struct cache_node *cache_node;
|
||||||
struct evict_node *next;
|
struct evict_node *next;
|
||||||
b32 evicted;
|
};
|
||||||
|
|
||||||
|
struct evict_bucket {
|
||||||
|
b32 contains_evicted_node;
|
||||||
|
struct cache_bucket *cache_bucket;
|
||||||
|
struct evict_node *head;
|
||||||
|
struct evict_bucket *next;
|
||||||
};
|
};
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
struct temp_arena scratch = scratch_begin_no_conflict();
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
||||||
struct evict_node *head_evict_node = NULL;
|
struct evict_bucket *head_evict_bucket = NULL;
|
||||||
|
b32 any_node_evicted = false;
|
||||||
|
|
||||||
b32 abort_thread_loop = false;
|
b32 abort_thread_loop = false;
|
||||||
sys_mutex_lock(&G.evictor_mutex);
|
sys_mutex_lock(&G.evictor_mutex);
|
||||||
@ -635,122 +663,145 @@ INTERNAL SYS_THREAD_ENTRY_POINT_FUNC_DEF(sheet_evictor_thread_entry_point, arg)
|
|||||||
sys_timestamp_t cur_timestamp = sys_timestamp();
|
sys_timestamp_t cur_timestamp = sys_timestamp();
|
||||||
f64 cur_time = sys_timestamp_seconds(cur_timestamp);
|
f64 cur_time = sys_timestamp_seconds(cur_timestamp);
|
||||||
|
|
||||||
sys_rw_mutex_lock_shared(&G.cache.rw_mutex);
|
/* Scan for evictable nodes */
|
||||||
{
|
{
|
||||||
__profscope(eviction_scan);
|
__profscope(eviction_scan);
|
||||||
|
|
||||||
for (u64 i = 0; i < CACHE_BUCKETS_COUNT; ++i) {
|
for (u64 i = 0; i < CACHE_BUCKETS_COUNT; ++i) {
|
||||||
struct cache_node *n = G.cache.buckets[i];
|
struct cache_bucket *bucket = &G.cache.buckets[i];
|
||||||
|
struct evict_node *head_evict_node = NULL;
|
||||||
|
sys_rw_mutex_lock_shared(&bucket->rw_mutex);
|
||||||
|
{
|
||||||
|
struct cache_node *n = bucket->first;
|
||||||
|
while (n) {
|
||||||
|
b32 should_evict_node = false;
|
||||||
|
|
||||||
while (n) {
|
if ((atomic_u32_eval(&n->state) == CACHE_NODE_STATE_LOADED) && (atomic_i32_eval(&n->refcount) <= 0)) {
|
||||||
b32 should_evict = false;
|
/* Check usage time */
|
||||||
|
if (!should_evict_node) {
|
||||||
if ((atomic_u32_eval(&n->state) == CACHE_NODE_STATE_LOADED) && (atomic_i32_eval(&n->refcount) <= 0)) {
|
/* TODO: Only evict if over memory budget (in LRU order until under memory budget) */
|
||||||
/* Check usage time */
|
sys_timestamp_t last_used_ts = atomic_u64_raw(&n->last_refcount0_ts);
|
||||||
if (!should_evict) {
|
f64 last_used_time = sys_timestamp_seconds(last_used_ts);
|
||||||
/* TODO: Only evict if over memory budget (in LRU order until under memory budget) */
|
if (cur_time - last_used_time > EVICTOR_GRACE_PERIOD) {
|
||||||
sys_timestamp_t last_used_ts = atomic_u64_raw(&n->last_refcount0_ts);
|
/* Cache entry unused for too long */
|
||||||
f64 last_used_time = sys_timestamp_seconds(last_used_ts);
|
should_evict_node = true;
|
||||||
if (cur_time - last_used_time > EVICTOR_GRACE_PERIOD) {
|
}
|
||||||
/* Cache entry unused for too long */
|
}
|
||||||
should_evict = true;
|
|
||||||
}
|
#if RESOURCE_RELOADING
|
||||||
}
|
/* Check for file changes for resource reloading */
|
||||||
|
if (!should_evict_node) {
|
||||||
#if RESOURCE_RELOADING
|
should_evict_node = true;
|
||||||
/* Check for file changes for resource reloading */
|
struct string path = string_from_cstr_len((char *)n->tag_path, n->tag_path_len);
|
||||||
if (!should_evict) {
|
if (sys_is_file(path)) {
|
||||||
should_evict = true;
|
struct sys_file file = sys_file_open_read(path);
|
||||||
struct string path = string_from_cstr_len((char *)n->tag_path, n->tag_path_len);
|
struct sys_file_time ft = sys_file_get_time(file);
|
||||||
if (sys_is_file(path)) {
|
sys_file_close(file);
|
||||||
struct sys_file file = sys_file_open_read(path);
|
|
||||||
struct sys_file_time ft = sys_file_get_time(file);
|
struct sys_datetime initial_file_time = n->initial_resource_file_modified_time;
|
||||||
sys_file_close(file);
|
struct sys_datetime current_file_time = ft.modified;
|
||||||
|
|
||||||
struct sys_datetime initial_file_time = n->initial_resource_file_modified_time;
|
if (MEMCMP_STRUCT(&initial_file_time, ¤t_file_time) != 0) {
|
||||||
struct sys_datetime current_file_time = ft.modified;
|
logf_info("Resource file for sheet \"%F\" has changed. Evicting to allow for reloading.", FMT_STR(path));
|
||||||
|
should_evict_node = true;
|
||||||
if (MEMCMP_STRUCT(&initial_file_time, ¤t_file_time) != 0) {
|
} else {
|
||||||
logf_info("Resource file for sheet \"%F\" has changed. Evicting to allow for reloading.", FMT_STR(path));
|
/* File unchanged */
|
||||||
should_evict = true;
|
should_evict_node = false;
|
||||||
} else {
|
}
|
||||||
/* File unchanged */
|
|
||||||
should_evict = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
/* Add node to bucket evict list */
|
||||||
|
if (should_evict_node) {
|
||||||
|
struct evict_node *evict_node = arena_push_zero(scratch.arena, struct evict_node);
|
||||||
|
evict_node->cache_node = n;
|
||||||
|
evict_node->next = head_evict_node;
|
||||||
|
head_evict_node = evict_node;
|
||||||
|
}
|
||||||
|
|
||||||
if (should_evict) {
|
n = n->next_hash;
|
||||||
/* Add to evict list */
|
|
||||||
struct evict_node *evict_node = arena_push_zero(scratch.arena, struct evict_node);
|
|
||||||
evict_node->cache_node = n;
|
|
||||||
evict_node->next = head_evict_node;
|
|
||||||
head_evict_node = evict_node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
n = n->next_hash;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sys_rw_mutex_unlock_shared(&bucket->rw_mutex);
|
||||||
|
|
||||||
|
/* Add bucket with nodes to evict list */
|
||||||
|
if (head_evict_node) {
|
||||||
|
struct evict_bucket *evict_bucket = arena_push_zero(scratch.arena, struct evict_bucket);
|
||||||
|
evict_bucket->cache_bucket = bucket;
|
||||||
|
evict_bucket->head = head_evict_node;
|
||||||
|
evict_bucket->next = head_evict_bucket;
|
||||||
|
head_evict_bucket = evict_bucket;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sys_rw_mutex_unlock_shared(&G.cache.rw_mutex);
|
|
||||||
|
|
||||||
if (head_evict_node) {
|
/* Remove evictable nodes from cache table */
|
||||||
/* Remove evictable nodes from cache table */
|
{
|
||||||
sys_rw_mutex_lock_exclusive(&G.cache.rw_mutex);
|
__profscope(eviction_cache_removal);
|
||||||
{
|
for (struct evict_bucket *eb = head_evict_bucket; eb; eb = eb->next) {
|
||||||
__profscope(eviction_cache_removal);
|
struct cache_bucket *bucket = eb->cache_bucket;
|
||||||
for (struct evict_node *en = head_evict_node; en; en = en->next) {
|
sys_rw_mutex_lock_exclusive(&bucket->rw_mutex);
|
||||||
struct cache_node *n = en->cache_node;
|
{
|
||||||
/* Check that cache node is still loaded and unreferenced */
|
for (struct evict_node *en = eb->head; en; en = en->next) {
|
||||||
if ((atomic_u32_eval(&n->state) == CACHE_NODE_STATE_LOADED) && (atomic_i32_eval(&n->refcount) <= 0)) {
|
struct cache_node *n = en->cache_node;
|
||||||
/* Remove from cache table */
|
/* Check that cache node is still loaded and unreferenced */
|
||||||
if (n->prev_hash) {
|
if ((atomic_u32_eval(&n->state) == CACHE_NODE_STATE_LOADED) && (atomic_i32_eval(&n->refcount) <= 0)) {
|
||||||
n->prev_hash->next_hash = n->next_hash;
|
/* Remove from cache table */
|
||||||
} else {
|
if (n->prev_hash) {
|
||||||
u64 cache_bucket_index = n->hash % CACHE_BUCKETS_COUNT;
|
n->prev_hash->next_hash = n->next_hash;
|
||||||
G.cache.buckets[cache_bucket_index] = n->next_hash;
|
} else {
|
||||||
|
bucket->first = n->next_hash;
|
||||||
|
}
|
||||||
|
if (n->next_hash) {
|
||||||
|
n->next_hash->prev_hash = n->prev_hash;
|
||||||
|
}
|
||||||
|
en->evicted = true;
|
||||||
|
eb->contains_evicted_node = true;
|
||||||
|
any_node_evicted = true;
|
||||||
}
|
}
|
||||||
if (n->next_hash) {
|
|
||||||
n->next_hash->prev_hash = n->prev_hash;
|
|
||||||
}
|
|
||||||
en->evicted = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sys_rw_mutex_unlock_exclusive(&bucket->rw_mutex);
|
||||||
}
|
}
|
||||||
sys_rw_mutex_unlock_exclusive(&G.cache.rw_mutex);
|
}
|
||||||
|
|
||||||
/* Free evicted node memory */
|
if (any_node_evicted) {
|
||||||
{
|
/* Release evicted node memory */
|
||||||
__profscope(eviction_memory_release);
|
__profscope(eviction_memory_release);
|
||||||
for (struct evict_node *en = head_evict_node; en; en = en->next) {
|
for (struct evict_bucket *eb = head_evict_bucket; eb; eb = eb->next) {
|
||||||
if (en->evicted) {
|
if (eb->contains_evicted_node) {
|
||||||
struct cache_node *n = en->cache_node;
|
for (struct evict_node *en = eb->head; en; en = en->next) {
|
||||||
arena_release(&n->arena);
|
if (en->evicted) {
|
||||||
|
struct cache_node *n = en->cache_node;
|
||||||
|
arena_release(&n->arena);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add evicted nodes to free list */
|
/* Add evicted nodes to free list */
|
||||||
/* TODO: Create and lock separate mutex for free list */
|
__profscope(eviction_free_list_append);
|
||||||
sys_rw_mutex_lock_exclusive(&G.cache.rw_mutex);
|
sys_mutex_lock(&G.cache.node_pool_mutex);
|
||||||
{
|
{
|
||||||
__profscope(eviction_free_list_append);
|
for (struct evict_bucket *eb = head_evict_bucket; eb; eb = eb->next) {
|
||||||
for (struct evict_node *en = head_evict_node; en; en = en->next) {
|
if (eb->contains_evicted_node) {
|
||||||
struct cache_node *n = en->cache_node;
|
for (struct evict_node *en = eb->head; en; en = en->next) {
|
||||||
if (en->evicted) {
|
if (en->evicted) {
|
||||||
n->next_free = G.cache.first_free;
|
struct cache_node *n = en->cache_node;
|
||||||
G.cache.first_free = n;
|
n->next_free = G.cache.node_pool_first_free;
|
||||||
|
G.cache.node_pool_first_free = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sys_rw_mutex_unlock_exclusive(&G.cache.rw_mutex);
|
sys_mutex_unlock(&G.cache.node_pool_mutex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
abort_thread_loop:
|
abort_thread_loop:
|
||||||
sys_mutex_unlock(&G.evictor_mutex);
|
|
||||||
|
|
||||||
|
sys_mutex_unlock(&G.evictor_mutex);
|
||||||
scratch_end(scratch);
|
scratch_end(scratch);
|
||||||
|
|
||||||
if (abort_thread_loop) {
|
if (abort_thread_loop) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user