From: Ben Peart <peartben@gmail.com>
To: git@vger.kernel.org
Cc: gitster@pobox.com, pclouds@gmail.com, peartben@gmail.com,
Ben Peart <benpeart@microsoft.com>
Subject: [PATCH v2] checkout: eliminate unnecessary merge for trivial checkout
Date: Fri, 9 Sep 2016 15:25:20 -0400 [thread overview]
Message-ID: <20160909192520.4812-1-benpeart@microsoft.com> (raw)
Teach git to avoid unnecessary merge during trivial checkout. When
running 'git checkout -b foo' git follows a common code path through
the expensive merge_working_tree even when it is unnecessary. As a
result, 95% of the time is spent in merge_working_tree doing the 2-way
merge between the new and old commit trees that is unneeded.
The time breakdown is as follows:
merge_working_tree <-- 95%
unpack_trees <-- 80%
traverse_trees <-- 50%
cache_tree_update <-- 17%
mark_new_skip_worktree <-- 10%
With a large repo, this cost is pronounced. Using "git checkout -b r"
to create and switch to a new branch costs 166 seconds (all times worst
case with a cold file system cache).
git.c:406 trace: built-in: git 'checkout' '-b' 'r'
read-cache.c:1667 performance: 17.442926555 s: read_index_from
name-hash.c:128 performance: 2.912145231 s: lazy_init_name_hash
read-cache.c:2208 performance: 4.387713335 s: write_locked_index
trace.c:420 performance: 166.458921289 s: git command:
'c:\Users\benpeart\bin\git.exe' 'checkout' '-b' 'r'
Switched to a new branch 'r'
By adding a test to skip the unnecessary call to merge_working_tree in
this case reduces the cost to 16 seconds.
git.c:406 trace: built-in: git 'checkout' '-b' 's'
read-cache.c:1667 performance: 16.100742476 s: read_index_from
trace.c:420 performance: 16.461547867 s: git command: 'c:\Users\benpeart\bin\git.exe' 'checkout' '-b' 's'
Switched to a new branch 's'
Signed-off-by: Ben Peart <benpeart@microsoft.com>
---
builtin/checkout.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 95 insertions(+), 4 deletions(-)
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 8672d07..4396cb3 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -38,6 +38,10 @@ struct checkout_opts {
int ignore_skipworktree;
int ignore_other_worktrees;
int show_progress;
+ /*
+ * If new checkout options are added, needs_working_tree_merge
+ * should be updated accordingly.
+ */
const char *new_branch;
const char *new_branch_force;
@@ -802,6 +806,87 @@ static void orphaned_commit_warning(struct commit *old, struct commit *new)
free(refs.objects);
}
+static int needs_working_tree_merge(const struct checkout_opts *opts,
+ const struct branch_info *old,
+ const struct branch_info *new)
+{
+ /*
+ * We must do the merge if we are actually moving to a new
+ * commit tree.
+ */
+ if (!old->commit || !new->commit ||
+ oidcmp(&old->commit->tree->object.oid, &new->commit->tree->object.oid))
+ return 1;
+
+ /*
+ * opts->patch_mode cannot be used with switching branches so is
+ * not tested here
+ */
+
+ /*
+ * opts->quiet only impacts output so doesn't require a merge
+ */
+
+ /*
+ * Honor the explicit request for a three-way merge or to throw away
+ * local changes
+ */
+ if (opts->merge || opts->force)
+ return 1;
+
+ /*
+ * Checking out the requested commit may require updating the working
+ * directory and index, let the merge handle it.
+ */
+ if (opts->force_detach)
+ return 1;
+
+ /*
+ * opts->writeout_stage cannot be used with switching branches so is
+ * not tested here
+ */
+
+ /*
+ * Honor the explicit ignore requests
+ */
+ if (!opts->overwrite_ignore || opts->ignore_skipworktree
+ || opts->ignore_other_worktrees)
+ return 1;
+
+ /*
+ * opts->show_progress only impacts output so doesn't require a merge
+ */
+
+ /*
+ * If we're not creating a new branch, by definition we're changing
+ * the existing one so need to do the merge
+ */
+ if (!opts->new_branch)
+ return 1;
+
+ /*
+ * new_branch_force is defined to "create/reset and checkout a branch"
+ * so needs to go through the merge to do the reset
+ */
+ if (opts->new_branch_force)
+ return 1;
+
+ /*
+ * A new orphaned branch requrires the index and the working tree to be
+ * adjusted to <start_point>
+ */
+ if (opts->new_orphan_branch)
+ return 1;
+
+ /*
+ * Remaining variables are not checkout options but used to track state
+ * that doesn't trigger the need for a merge.
+ */
+
+ return 0;
+}
+
+
static int switch_branches(const struct checkout_opts *opts,
struct branch_info *new)
{
@@ -827,10 +912,16 @@ static int switch_branches(const struct checkout_opts *opts,
parse_commit_or_die(new->commit);
}
- ret = merge_working_tree(opts, &old, new, &writeout_error);
- if (ret) {
- free(path_to_free);
- return ret;
+ /*
+ * Optimize the performance of "git checkout foo" by skipping the call
+ * to merge_working_tree where possible.
+ */
+ if (needs_working_tree_merge(opts, &old, new)) {
+ ret = merge_working_tree(opts, &old, new, &writeout_error);
+ if (ret) {
+ free(path_to_free);
+ return ret;
+ }
}
if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
--
2.10.0.windows.1
next reply other threads:[~2016-09-09 19:25 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-09-09 19:25 Ben Peart [this message]
2016-09-09 21:55 ` [PATCH v2] checkout: eliminate unnecessary merge for trivial checkout Junio C Hamano
2016-09-12 18:12 ` Ben Peart
2016-09-12 20:31 ` Junio C Hamano
2016-09-13 12:33 ` Ben Peart
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20160909192520.4812-1-benpeart@microsoft.com \
--to=peartben@gmail.com \
--cc=benpeart@microsoft.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=pclouds@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.