diff options
Diffstat (limited to 'src/cache.c')
-rw-r--r-- | src/cache.c | 261 |
1 files changed, 206 insertions, 55 deletions
diff --git a/src/cache.c b/src/cache.c index e7f333577..ca122fb77 100644 --- a/src/cache.c +++ b/src/cache.c @@ -11,100 +11,251 @@ #include "thread-utils.h" #include "util.h" #include "cache.h" +#include "odb.h" +#include "object.h" #include "git2/oid.h" -int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr) +GIT__USE_OIDMAP + +bool git_cache__enabled = true; +size_t git_cache__max_storage = (4 * 1024 * 1024); + +static size_t git_cache__max_object_size[8] = { + 0, /* GIT_OBJ__EXT1 */ + 4096, /* GIT_OBJ_COMMIT */ + 4096, /* GIT_OBJ_TREE */ + 0, /* GIT_OBJ_BLOB */ + 4096, /* GIT_OBJ_TAG */ + 0, /* GIT_OBJ__EXT2 */ + 0, /* GIT_OBJ_OFS_DELTA */ + 0 /* GIT_OBJ_REF_DELTA */ +}; + +int git_cache_set_max_object_size(git_otype type, size_t size) { - if (size < 8) - size = 8; - size = git__size_t_powerof2(size); + if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) { + giterr_set(GITERR_INVALID, "type out of range"); + return -1; + } + + git_cache__max_object_size[type] = size; + return 0; +} - cache->size_mask = size - 1; - cache->lru_count = 0; - cache->free_obj = free_ptr; +void git_cache_dump_stats(git_cache *cache) +{ + git_cached_obj *object; - git_mutex_init(&cache->lock); + if (kh_size(cache->map) == 0) + return; - cache->nodes = git__malloc(size * sizeof(git_cached_obj *)); - GITERR_CHECK_ALLOC(cache->nodes); + printf("Cache %p: %d items cached, %d bytes\n", + cache, kh_size(cache->map), (int)cache->used_memory); - memset(cache->nodes, 0x0, size * sizeof(git_cached_obj *)); + kh_foreach_value(cache->map, object, { + char oid_str[9]; + printf(" %s%c %s (%d)\n", + git_object_type2string(object->type), + object->flags == GIT_CACHE_STORE_PARSED ? '*' : ' ', + git_oid_tostr(oid_str, sizeof(oid_str), &object->oid), + (int)object->size + ); + }); +} + +int git_cache_init(git_cache *cache) +{ + cache->used_memory = 0; + cache->map = git_oidmap_alloc(); + git_mutex_init(&cache->lock); return 0; } +/* called with lock */ +static void clear_cache(git_cache *cache) +{ + git_cached_obj *evict = NULL; + + kh_foreach_value(cache->map, evict, { + git_cached_obj_decref(evict); + }); + + kh_clear(oid, cache->map); + cache->used_memory = 0; +} + +void git_cache_clear(git_cache *cache) +{ + if (git_mutex_lock(&cache->lock) < 0) + return; + + clear_cache(cache); + + git_mutex_unlock(&cache->lock); +} + void git_cache_free(git_cache *cache) { - size_t i; + git_cache_clear(cache); + + git_oidmap_free(cache->map); + git_mutex_free(&cache->lock); +} + +/* Called with lock */ +static void cache_evict_entries(git_cache *cache) +{ + uint32_t seed = rand(); + size_t evict_count = 8; - for (i = 0; i < (cache->size_mask + 1); ++i) { - if (cache->nodes[i] != NULL) - git_cached_obj_decref(cache->nodes[i], cache->free_obj); + /* do not infinite loop if there's not enough entries to evict */ + if (evict_count > kh_size(cache->map)) { + clear_cache(cache); + return; } - git_mutex_free(&cache->lock); - git__free(cache->nodes); + while (evict_count > 0) { + khiter_t pos = seed++ % kh_end(cache->map); + + if (kh_exist(cache->map, pos)) { + git_cached_obj *evict = kh_val(cache->map, pos); + + evict_count--; + cache->used_memory -= evict->size; + git_cached_obj_decref(evict); + + kh_del(oid, cache->map, pos); + } + } } -void *git_cache_get(git_cache *cache, const git_oid *oid) +static bool cache_should_store(git_otype object_type, size_t object_size) { - uint32_t hash; - git_cached_obj *node = NULL, *result = NULL; + size_t max_size = git_cache__max_object_size[object_type]; + return git_cache__enabled && object_size < max_size; +} - memcpy(&hash, oid->id, sizeof(hash)); +static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags) +{ + khiter_t pos; + git_cached_obj *entry = NULL; - if (git_mutex_lock(&cache->lock)) { - giterr_set(GITERR_THREAD, "unable to lock cache mutex"); + if (!git_cache__enabled || git_mutex_lock(&cache->lock) < 0) return NULL; - } - { - node = cache->nodes[hash & cache->size_mask]; + pos = kh_get(oid, cache->map, oid); + if (pos != kh_end(cache->map)) { + entry = kh_val(cache->map, pos); - if (node != NULL && git_oid_cmp(&node->oid, oid) == 0) { - git_cached_obj_incref(node); - result = node; + if (flags && entry->flags != flags) { + entry = NULL; + } else { + git_cached_obj_incref(entry); } } + git_mutex_unlock(&cache->lock); - return result; + return entry; } -void *git_cache_try_store(git_cache *cache, void *_entry) +static void *cache_store(git_cache *cache, git_cached_obj *entry) { - git_cached_obj *entry = _entry; - uint32_t hash; + khiter_t pos; - memcpy(&hash, &entry->oid, sizeof(uint32_t)); + git_cached_obj_incref(entry); - if (git_mutex_lock(&cache->lock)) { - giterr_set(GITERR_THREAD, "unable to lock cache mutex"); - return NULL; - } + if (!cache_should_store(entry->type, entry->size)) + return entry; - { - git_cached_obj *node = cache->nodes[hash & cache->size_mask]; + if (git_mutex_lock(&cache->lock) < 0) + return entry; - /* increase the refcount on this object, because - * the cache now owns it */ - git_cached_obj_incref(entry); + if (cache->used_memory > git_cache__max_storage) + cache_evict_entries(cache); - if (node == NULL) { - cache->nodes[hash & cache->size_mask] = entry; - } else if (git_oid_cmp(&node->oid, &entry->oid) == 0) { - git_cached_obj_decref(entry, cache->free_obj); - entry = node; - } else { - git_cached_obj_decref(node, cache->free_obj); - cache->nodes[hash & cache->size_mask] = entry; + pos = kh_get(oid, cache->map, &entry->oid); + + /* not found */ + if (pos == kh_end(cache->map)) { + int rval; + + pos = kh_put(oid, cache->map, &entry->oid, &rval); + if (rval >= 0) { + kh_key(cache->map, pos) = &entry->oid; + kh_val(cache->map, pos) = entry; + git_cached_obj_incref(entry); + cache->used_memory += entry->size; } + } + /* found */ + else { + git_cached_obj *stored_entry = kh_val(cache->map, pos); - /* increase the refcount again, because we are - * returning it to the user */ - git_cached_obj_incref(entry); + if (stored_entry->flags == entry->flags) { + git_cached_obj_decref(entry); + git_cached_obj_incref(stored_entry); + entry = stored_entry; + } else if (stored_entry->flags == GIT_CACHE_STORE_RAW && + entry->flags == GIT_CACHE_STORE_PARSED) { + git_cached_obj_decref(stored_entry); + git_cached_obj_incref(entry); + kh_key(cache->map, pos) = &entry->oid; + kh_val(cache->map, pos) = entry; + } else { + /* NO OP */ + } } - git_mutex_unlock(&cache->lock); + git_mutex_unlock(&cache->lock); return entry; } + +void *git_cache_store_raw(git_cache *cache, git_odb_object *entry) +{ + entry->cached.flags = GIT_CACHE_STORE_RAW; + return cache_store(cache, (git_cached_obj *)entry); +} + +void *git_cache_store_parsed(git_cache *cache, git_object *entry) +{ + entry->cached.flags = GIT_CACHE_STORE_PARSED; + return cache_store(cache, (git_cached_obj *)entry); +} + +git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid) +{ + return cache_get(cache, oid, GIT_CACHE_STORE_RAW); +} + +git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid) +{ + return cache_get(cache, oid, GIT_CACHE_STORE_PARSED); +} + +void *git_cache_get_any(git_cache *cache, const git_oid *oid) +{ + return cache_get(cache, oid, GIT_CACHE_STORE_ANY); +} + +void git_cached_obj_decref(void *_obj) +{ + git_cached_obj *obj = _obj; + + if (git_atomic_dec(&obj->refcount) == 0) { + switch (obj->flags) { + case GIT_CACHE_STORE_RAW: + git_odb_object__free(_obj); + break; + + case GIT_CACHE_STORE_PARSED: + git_object__free(_obj); + break; + + default: + git__free(_obj); + break; + } + } +} |