Merge branch 'nd/unpack-trees-with-cache-tree'

The unpack_trees() API used in checking out a branch and merging
walks one or more trees along with the index.  When the cache-tree
in the index tells us that we are walking a tree whose flattened
contents is known (i.e. matches a span in the index), as linearly
scanning a span in the index is much more efficient than having to
open tree objects recursively and listing their entries, the walk
can be optimized, which is done in this topic.

* nd/unpack-trees-with-cache-tree:
  Document update for nd/unpack-trees-with-cache-tree
  cache-tree: verify valid cache-tree in the test suite
  unpack-trees: add missing cache invalidation
  unpack-trees: reuse (still valid) cache-tree from src_index
  unpack-trees: reduce malloc in cache-tree walk
  unpack-trees: optimize walking same trees with cache-tree
  unpack-trees: add performance tracing
  trace.h: support nested performance tracing
This commit is contained in:
Junio C Hamano
2018-09-17 13:53:53 -07:00
12 changed files with 344 additions and 22 deletions

View File

@ -385,6 +385,7 @@ static int check_updates(struct unpack_trees_options *o)
struct checkout state = CHECKOUT_INIT;
int i;
trace_performance_enter();
state.force = 1;
state.quiet = 1;
state.refresh_cache = 1;
@ -461,6 +462,7 @@ static int check_updates(struct unpack_trees_options *o)
if (o->clone)
report_collided_checkout(index);
trace_performance_leave("check_updates");
return errs != 0;
}
@ -680,6 +682,113 @@ static inline int are_same_oid(struct name_entry *name_j, struct name_entry *nam
return name_j->oid && name_k->oid && !oidcmp(name_j->oid, name_k->oid);
}
static int all_trees_same_as_cache_tree(int n, unsigned long dirmask,
struct name_entry *names,
struct traverse_info *info)
{
struct unpack_trees_options *o = info->data;
int i;
if (!o->merge || dirmask != ((1 << n) - 1))
return 0;
for (i = 1; i < n; i++)
if (!are_same_oid(names, names + i))
return 0;
return cache_tree_matches_traversal(o->src_index->cache_tree, names, info);
}
static int index_pos_by_traverse_info(struct name_entry *names,
struct traverse_info *info)
{
struct unpack_trees_options *o = info->data;
int len = traverse_path_len(info, names);
char *name = xmalloc(len + 1 /* slash */ + 1 /* NUL */);
int pos;
make_traverse_path(name, info, names);
name[len++] = '/';
name[len] = '\0';
pos = index_name_pos(o->src_index, name, len);
if (pos >= 0)
BUG("This is a directory and should not exist in index");
pos = -pos - 1;
if (!starts_with(o->src_index->cache[pos]->name, name) ||
(pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name)))
BUG("pos must point at the first entry in this directory");
free(name);
return pos;
}
/*
* Fast path if we detect that all trees are the same as cache-tree at this
* path. We'll walk these trees in an iterative loop using cache-tree/index
* instead of ODB since we already know what these trees contain.
*/
static int traverse_by_cache_tree(int pos, int nr_entries, int nr_names,
struct name_entry *names,
struct traverse_info *info)
{
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
struct unpack_trees_options *o = info->data;
struct cache_entry *tree_ce = NULL;
int ce_len = 0;
int i, d;
if (!o->merge)
BUG("We need cache-tree to do this optimization");
/*
* Do what unpack_callback() and unpack_nondirectories() normally
* do. But we walk all paths in an iterative loop instead.
*
* D/F conflicts and higher stage entries are not a concern
* because cache-tree would be invalidated and we would never
* get here in the first place.
*/
for (i = 0; i < nr_entries; i++) {
int new_ce_len, len, rc;
src[0] = o->src_index->cache[pos + i];
len = ce_namelen(src[0]);
new_ce_len = cache_entry_size(len);
if (new_ce_len > ce_len) {
new_ce_len <<= 1;
tree_ce = xrealloc(tree_ce, new_ce_len);
memset(tree_ce, 0, new_ce_len);
ce_len = new_ce_len;
tree_ce->ce_flags = create_ce_flags(0);
for (d = 1; d <= nr_names; d++)
src[d] = tree_ce;
}
tree_ce->ce_mode = src[0]->ce_mode;
tree_ce->ce_namelen = len;
oidcpy(&tree_ce->oid, &src[0]->oid);
memcpy(tree_ce->name, src[0]->name, len + 1);
rc = call_unpack_fn((const struct cache_entry * const *)src, o);
if (rc < 0) {
free(tree_ce);
return rc;
}
mark_ce_used(src[0], o);
}
free(tree_ce);
if (o->debug_unpack)
printf("Unpacked %d entries from %s to %s using cache-tree\n",
nr_entries,
o->src_index->cache[pos]->name,
o->src_index->cache[pos + nr_entries - 1]->name);
return 0;
}
static int traverse_trees_recursive(int n, unsigned long dirmask,
unsigned long df_conflicts,
struct name_entry *names,
@ -691,6 +800,27 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
void *buf[MAX_UNPACK_TREES];
struct traverse_info newinfo;
struct name_entry *p;
int nr_entries;
nr_entries = all_trees_same_as_cache_tree(n, dirmask, names, info);
if (nr_entries > 0) {
struct unpack_trees_options *o = info->data;
int pos = index_pos_by_traverse_info(names, info);
if (!o->merge || df_conflicts)
BUG("Wrong condition to get here buddy");
/*
* All entries up to 'pos' must have been processed
* (i.e. marked CE_UNPACKED) at this point. But to be safe,
* save and restore cache_bottom anyway to not miss
* unprocessed entries before 'pos'.
*/
bottom = o->cache_bottom;
ret = traverse_by_cache_tree(pos, nr_entries, n, names, info);
o->cache_bottom = bottom;
return ret;
}
p = names;
while (!p->mode)
@ -857,6 +987,11 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info,
return ce;
}
/*
* Note that traverse_by_cache_tree() duplicates some logic in this function
* without actually calling it. If you change the logic here you may need to
* check and change there as well.
*/
static int unpack_nondirectories(int n, unsigned long mask,
unsigned long dirmask,
struct cache_entry **src,
@ -1049,6 +1184,11 @@ static void debug_unpack_callback(int n,
debug_name_entry(i, names + i);
}
/*
* Note that traverse_by_cache_tree() duplicates some logic in this function
* without actually calling it. If you change the logic here you may need to
* check and change there as well.
*/
static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info)
{
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
@ -1336,6 +1476,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
if (len > MAX_UNPACK_TREES)
die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
trace_performance_enter();
memset(&el, 0, sizeof(el));
if (!core_apply_sparse_checkout || !o->update)
o->skip_sparse_checkout = 1;
@ -1408,7 +1549,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
}
}
if (traverse_trees(len, t, &info) < 0)
trace_performance_enter();
ret = traverse_trees(len, t, &info);
trace_performance_leave("traverse_trees");
if (ret < 0)
goto return_failed;
}
@ -1483,7 +1627,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
ret = check_updates(o) ? (-2) : 0;
if (o->dst_index) {
move_index_extensions(&o->result, o->src_index);
if (!ret) {
if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
cache_tree_verify(&o->result);
if (!o->result.cache_tree)
o->result.cache_tree = cache_tree();
if (!cache_tree_fully_valid(o->result.cache_tree))
@ -1491,7 +1638,6 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
WRITE_TREE_SILENT |
WRITE_TREE_REPAIR);
}
move_index_extensions(&o->result, o->src_index);
discard_index(o->dst_index);
*o->dst_index = o->result;
} else {
@ -1500,6 +1646,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
o->src_index = NULL;
done:
trace_performance_leave("unpack_trees");
clear_exclude_list(&el);
return ret;
@ -1691,6 +1838,7 @@ static int verify_clean_subdirectory(const struct cache_entry *ce,
if (verify_uptodate(ce2, o))
return -1;
add_entry(o, ce2, CE_REMOVE, 0);
invalidate_ce_path(ce, o);
mark_ce_used(ce2, o);
}
cnt++;
@ -1950,6 +2098,8 @@ static int keep_entry(const struct cache_entry *ce,
struct unpack_trees_options *o)
{
add_entry(o, ce, 0, 0);
if (ce_stage(ce))
invalidate_ce_path(ce, o);
return 1;
}