Skip to content

Commit 589bed1

Browse files
pcloudsgitster
authored andcommitted
unpack-trees: optimize walking same trees with cache-tree
In order to merge one or many trees with the index, unpack-trees code walks multiple trees in parallel with the index and performs n-way merge. If we find out at start of a directory that all trees are the same (by comparing OID) and cache-tree happens to be available for that directory as well, we could avoid walking the trees because we already know what these trees contain: it's flattened in what's called "the index". The upside is of course a lot less I/O since we can potentially skip lots of trees (think subtrees). We also save CPU because we don't have to inflate and the apply deltas. The downside is of course more fragile code since the logic in some functions are now duplicated elsewhere. "checkout -" with this patch on gcc.git: baseline new -------------------------------------------------------------------- 0.018239226 0.019365414 s: read cache .git/index 0.052541655 0.049605548 s: preload index 0.001537598 0.001571695 s: refresh index 0.168167768 0.049677212 s: unpack trees 0.002897186 0.002845256 s: update worktree after a merge 0.131661745 0.136597522 s: repair cache-tree 0.075389117 0.075422517 s: write index, changed mask = 2a 0.111702023 0.032813253 s: unpack trees 0.000023245 0.000022002 s: update worktree after a merge 0.111793866 0.032933140 s: diff-index 0.587933288 0.398924370 s: git command: /home/pclouds/w/git/git This command calls unpack_trees() twice, the first time on 2way merge and the second 1way merge. In both times, "unpack trees" time is reduced to one third. Overall time reduction is not that impressive of course because index operations take a big chunk. And there's that repair cache-tree line. Signed-off-by: Nguyễn Thái Ngọc Duy <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 789f7e2 commit 589bed1

File tree

1 file changed

+118
-1
lines changed

1 file changed

+118
-1
lines changed

unpack-trees.c

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,102 @@ static inline int are_same_oid(struct name_entry *name_j, struct name_entry *nam
644644
return name_j->oid && name_k->oid && !oidcmp(name_j->oid, name_k->oid);
645645
}
646646

647+
static int all_trees_same_as_cache_tree(int n, unsigned long dirmask,
648+
struct name_entry *names,
649+
struct traverse_info *info)
650+
{
651+
struct unpack_trees_options *o = info->data;
652+
int i;
653+
654+
if (!o->merge || dirmask != ((1 << n) - 1))
655+
return 0;
656+
657+
for (i = 1; i < n; i++)
658+
if (!are_same_oid(names, names + i))
659+
return 0;
660+
661+
return cache_tree_matches_traversal(o->src_index->cache_tree, names, info);
662+
}
663+
664+
static int index_pos_by_traverse_info(struct name_entry *names,
665+
struct traverse_info *info)
666+
{
667+
struct unpack_trees_options *o = info->data;
668+
int len = traverse_path_len(info, names);
669+
char *name = xmalloc(len + 1 /* slash */ + 1 /* NUL */);
670+
int pos;
671+
672+
make_traverse_path(name, info, names);
673+
name[len++] = '/';
674+
name[len] = '\0';
675+
pos = index_name_pos(o->src_index, name, len);
676+
if (pos >= 0)
677+
BUG("This is a directory and should not exist in index");
678+
pos = -pos - 1;
679+
if (!starts_with(o->src_index->cache[pos]->name, name) ||
680+
(pos > 0 && starts_with(o->src_index->cache[pos-1]->name, name)))
681+
BUG("pos must point at the first entry in this directory");
682+
free(name);
683+
return pos;
684+
}
685+
686+
/*
687+
* Fast path if we detect that all trees are the same as cache-tree at this
688+
* path. We'll walk these trees recursively using cache-tree/index instead of
689+
* ODB since already know what these trees contain.
690+
*/
691+
static int traverse_by_cache_tree(int pos, int nr_entries, int nr_names,
692+
struct name_entry *names,
693+
struct traverse_info *info)
694+
{
695+
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
696+
struct unpack_trees_options *o = info->data;
697+
int i, d;
698+
699+
if (!o->merge)
700+
BUG("We need cache-tree to do this optimization");
701+
702+
/*
703+
* Do what unpack_callback() and unpack_nondirectories() normally
704+
* do. But we walk all paths recursively in just one loop instead.
705+
*
706+
* D/F conflicts and staged entries are not a concern because
707+
* cache-tree would be invalidated and we would never get here
708+
* in the first place.
709+
*/
710+
for (i = 0; i < nr_entries; i++) {
711+
struct cache_entry *tree_ce;
712+
int len, rc;
713+
714+
src[0] = o->src_index->cache[pos + i];
715+
716+
len = ce_namelen(src[0]);
717+
tree_ce = xcalloc(1, cache_entry_size(len));
718+
719+
tree_ce->ce_mode = src[0]->ce_mode;
720+
tree_ce->ce_flags = create_ce_flags(0);
721+
tree_ce->ce_namelen = len;
722+
oidcpy(&tree_ce->oid, &src[0]->oid);
723+
memcpy(tree_ce->name, src[0]->name, len + 1);
724+
725+
for (d = 1; d <= nr_names; d++)
726+
src[d] = tree_ce;
727+
728+
rc = call_unpack_fn((const struct cache_entry * const *)src, o);
729+
free(tree_ce);
730+
if (rc < 0)
731+
return rc;
732+
733+
mark_ce_used(src[0], o);
734+
}
735+
if (o->debug_unpack)
736+
printf("Unpacked %d entries from %s to %s using cache-tree\n",
737+
nr_entries,
738+
o->src_index->cache[pos]->name,
739+
o->src_index->cache[pos + nr_entries - 1]->name);
740+
return 0;
741+
}
742+
647743
static int traverse_trees_recursive(int n, unsigned long dirmask,
648744
unsigned long df_conflicts,
649745
struct name_entry *names,
@@ -655,6 +751,17 @@ static int traverse_trees_recursive(int n, unsigned long dirmask,
655751
void *buf[MAX_UNPACK_TREES];
656752
struct traverse_info newinfo;
657753
struct name_entry *p;
754+
int nr_entries;
755+
756+
nr_entries = all_trees_same_as_cache_tree(n, dirmask, names, info);
757+
if (nr_entries > 0) {
758+
struct unpack_trees_options *o = info->data;
759+
int pos = index_pos_by_traverse_info(names, info);
760+
761+
if (!o->merge || df_conflicts)
762+
BUG("Wrong condition to get here buddy");
763+
return traverse_by_cache_tree(pos, nr_entries, n, names, info);
764+
}
658765

659766
p = names;
660767
while (!p->mode)
@@ -814,6 +921,11 @@ static struct cache_entry *create_ce_entry(const struct traverse_info *info, con
814921
return ce;
815922
}
816923

924+
/*
925+
* Note that traverse_by_cache_tree() duplicates some logic in this funciton
926+
* without actually calling it. If you change the logic here you may need to
927+
* check and change there as well.
928+
*/
817929
static int unpack_nondirectories(int n, unsigned long mask,
818930
unsigned long dirmask,
819931
struct cache_entry **src,
@@ -998,6 +1110,11 @@ static void debug_unpack_callback(int n,
9981110
debug_name_entry(i, names + i);
9991111
}
10001112

1113+
/*
1114+
* Note that traverse_by_cache_tree() duplicates some logic in this funciton
1115+
* without actually calling it. If you change the logic here you may need to
1116+
* check and change there as well.
1117+
*/
10011118
static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info)
10021119
{
10031120
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
@@ -1280,7 +1397,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
12801397
uint64_t start = getnanotime();
12811398

12821399
if (len > MAX_UNPACK_TREES)
1283-
die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
1400+
die(_("unpack_trees takes at most %d trees"), MAX_UNPACK_TREES);
12841401

12851402
memset(&el, 0, sizeof(el));
12861403
if (!core_apply_sparse_checkout || !o->update)

0 commit comments

Comments
 (0)