Thanks to visit codestin.com
Credit goes to github.com

Skip to content

New caching #1454

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Apr 22, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 3 additions & 11 deletions include/git2/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@ enum {
GIT_OPT_SET_MWINDOW_MAPPED_LIMIT,
GIT_OPT_GET_SEARCH_PATH,
GIT_OPT_SET_SEARCH_PATH,
GIT_OPT_GET_ODB_CACHE_SIZE,
GIT_OPT_SET_ODB_CACHE_SIZE,
GIT_OPT_SET_CACHE_OBJECT_LIMIT,
GIT_OPT_SET_CACHE_MAX_SIZE,
GIT_OPT_ENABLE_CACHING
};

/**
Expand Down Expand Up @@ -169,15 +170,6 @@ enum {
* - `level` must be GIT_CONFIG_LEVEL_SYSTEM, GIT_CONFIG_LEVEL_GLOBAL,
* or GIT_CONFIG_LEVEL_XDG.
*
* opts(GIT_OPT_GET_ODB_CACHE_SIZE):
* Get the size of the libgit2 odb cache.
*
* opts(GIT_OPT_SET_ODB_CACHE_SIZE):
* Set the size of the of the libgit2 odb cache. This needs
* to be done before git_repository_open is called, since
* git_repository_open initializes the odb layer. Defaults
* to 128.
*
* @param option Option key
* @param ... value to set the option
* @return 0 on success, <0 on failure
Expand Down
20 changes: 11 additions & 9 deletions src/blob.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,34 @@
const void *git_blob_rawcontent(const git_blob *blob)
{
assert(blob);
return blob->odb_object->raw.data;
return git_odb_object_data(blob->odb_object);
}

git_off_t git_blob_rawsize(const git_blob *blob)
{
assert(blob);
return (git_off_t)blob->odb_object->raw.len;
return (git_off_t)git_odb_object_size(blob->odb_object);
}

int git_blob__getbuf(git_buf *buffer, git_blob *blob)
{
return git_buf_set(
buffer, blob->odb_object->raw.data, blob->odb_object->raw.len);
buffer,
git_odb_object_data(blob->odb_object),
git_odb_object_size(blob->odb_object));
}

void git_blob__free(git_blob *blob)
void git_blob__free(void *blob)
{
git_odb_object_free(blob->odb_object);
git_odb_object_free(((git_blob *)blob)->odb_object);
git__free(blob);
}

int git_blob__parse(git_blob *blob, git_odb_object *odb_obj)
int git_blob__parse(void *blob, git_odb_object *odb_obj)
{
assert(blob);
git_cached_obj_incref((git_cached_obj *)odb_obj);
blob->odb_object = odb_obj;
((git_blob *)blob)->odb_object = odb_obj;
return 0;
}

Expand Down Expand Up @@ -315,8 +317,8 @@ int git_blob_is_binary(git_blob *blob)

assert(blob);

content.ptr = blob->odb_object->raw.data;
content.size = min(blob->odb_object->raw.len, 4000);
content.ptr = blob->odb_object->buffer;
content.size = min(blob->odb_object->cached.size, 4000);

return git_buf_text_is_binary(&content);
}
4 changes: 2 additions & 2 deletions src/blob.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ struct git_blob {
git_odb_object *odb_object;
};

void git_blob__free(git_blob *blob);
int git_blob__parse(git_blob *blob, git_odb_object *obj);
void git_blob__free(void *blob);
int git_blob__parse(void *blob, git_odb_object *obj);
int git_blob__getbuf(git_buf *buffer, git_blob *blob);

#endif
261 changes: 206 additions & 55 deletions src/cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we try to evict as many as we can (say, evict_count = kh_size(cache->map)) instead of just ignoring it?

}

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;
}
}
}
Loading