Git development
 help / color / mirror / Atom feed
From: "SZEDER Gábor" <szeder.dev@gmail.com>
To: Patrick Steinhardt <ps@pks.im>
Cc: git@vger.kernel.org, Miklos Vajna <vmiklos@collabora.com>
Subject: Re: Performance regression in `git log --follow`
Date: Tue, 13 Oct 2026 23:24:41 +0200	[thread overview]
Message-ID: <as6hmUG5AaL5iLbO@szeder.dev> (raw)
In-Reply-To: <as44NVKZA9C9S5ae@pks.im>

On Tue, Oct 13, 2026 at 03:55:01PM +0200, Patrick Steinhardt wrote:
> Hi,
> 
> a user on IRC reported a performance regression in linux.git:
> 
>     $ git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
>     $ git -C linux log --stat --follow -- include/linux/rsc_table.h
> 
> I was able to reproduce the performance regression: while this command
> takes roughly 33 seconds with Git 2.55, it takes almost 8 minutes with
> Git 2.56.
> 
> I've bisected the regression to 304812ed33 (log: improve --follow
> following renames for non-linear history, 2026-06-22). I don't have time
> right now to debug any further, so I decided to just forward the report
> to the mailing list. I've also Cc'd the original author of the above
> commit.

The performance regression is clearly noticable in the git repository,
though the slowdown is not as pronounced as in linux.git:

  Benchmark 1: ./304812ed33^/bin/git --no-pager log --oneline --no-patch --follow v2.56.0 t/valgrind/valgrind.sh
    Time (mean ± σ):      3.275 s ±  0.022 s    [User: 3.157 s, System: 0.116 s]
    Range (min … max):    3.240 s …  3.313 s    10 runs

  Benchmark 2: ./304812ed33/bin/git --no-pager log --oneline --no-patch --follow v2.56.0 t/valgrind/valgrind.sh
    Time (mean ± σ):      8.825 s ±  0.029 s    [User: 8.554 s, System: 0.264 s]
    Range (min … max):    8.769 s …  8.864 s    10 runs

  Summary
    './304812ed33^/bin/git --no-pager log --oneline --no-patch --follow v2.56.0 t/valgrind/valgrind.sh' ran
      2.69 ± 0.02 times faster than './304812ed33/bin/git --no-pager log --oneline --no-patch --follow v2.56.0 t/valgrind/valgrind.sh'

That's 5.5 seconds of runtime difference, and with a bit of trace
performance hack[1] it turns out that most of it is spent in a call to
propagate_follow_pathspec_to_parent() to compare the current commit
with its second...N-th parent:

  $ GIT_TRACE_PERFORMANCE=1 ./304812ed33/bin/git --no-pager log --follow --oneline v2.56.0 t/valgrind/valgrind.sh >/dev/null
  21:59:30.465592 read-cache.c:2364       performance: 0.000626524 s:  read cache .git/index
  21:59:39.246697 log-tree.c:1158         performance: 1.374980154 s:  first parent propagate_follow_pathspec_to_parent()
  21:59:39.247164 log-tree.c:1159         performance: 4.448692273 s:  N-th parents propagate_follow_pathspec_to_parent()
  21:59:39.247192 trace.c:416             performance: 8.799236172 s: git command: ./304812ed33/bin/git --no-pager log --follow --oneline v2.56.0 t/valgrind/valgrind.sh

Interestingly, although line-level log performs its own rename
tracking, it's still notably faster than "--follow" even before this
perf regression (that "-L1,40" means "track the whole file"):

  Benchmark 1: ./304812ed33^/bin/git --no-pager log --oneline --no-patch --follow v2.56.0 t/valgrind/valgrind.sh
    Time (mean ± σ):      3.260 s ±  0.019 s    [User: 3.140 s, System: 0.118 s]
    Range (min … max):    3.219 s …  3.284 s    10 runs

  Benchmark 2: ./304812ed33^/bin/git --no-pager log --oneline --no-patch -L1,40:t/valgrind/valgrind.sh v2.56.0
    Time (mean ± σ):      1.454 s ±  0.014 s    [User: 1.369 s, System: 0.084 s]
    Range (min … max):    1.441 s …  1.481 s    10 runs

  Summary
    './304812ed33^/bin/git --no-pager log --oneline --no-patch -L1,40:t/valgrind/valgrind.sh v2.56.0' ran
      2.24 ± 0.02 times faster than './304812ed33^/bin/git --no-pager log --oneline --no-patch --follow v2.56.0 t/valgrind/valgrind.sh'

The reason for this might be:

  - If line-level log finds that the interesting path is not modified
    between a merge commit and its first parent, then it doesn't
    perform a diff between that merge commit and its second..N-th
    parent.
    "--follow" seems to performs a diff between a merge commit and all
    its parents.

  - Line-level log checks for renames only when the path is not
    present in the parent commit.
    "--follow" seems to always look for renames.


[1] It really is just a hack:

diff --git a/log-tree.c b/log-tree.c
index 83a3c4bf9b..fd4824fd3e 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -34,6 +34,7 @@
 #include "wildmatch.h"
 #include "write-or-die.h"
 #include "pager.h"
+#include "trace.h"
 
 static struct decoration name_decoration = { "object names" };
 static int decoration_loaded;
@@ -1144,6 +1145,8 @@ static void free_follow_pathspec_slot(char **slot)
 	FREE_AND_NULL(*slot);
 }
 
+static uint64_t t_first = 0, t_nth = 0;
+
 void release_follow_pathspec_slab(struct rev_info *opt)
 {
 	if (!opt->follow_pathspec_slab)
@@ -1151,6 +1154,9 @@ void release_follow_pathspec_slab(struct rev_info *opt)
 	deep_clear_follow_pathspec_slab(opt->follow_pathspec_slab,
 					free_follow_pathspec_slot);
 	FREE_AND_NULL(opt->follow_pathspec_slab);
+
+	trace_performance(t_first, "first parent propagate_follow_pathspec_to_parent()");
+	trace_performance(t_nth, "N-th parents propagate_follow_pathspec_to_parent()");
 }
 
 /* Compute a path to follow in parent, if there is one */
@@ -1304,9 +1310,18 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)
 		struct commit_list *parents = get_saved_parents(opt, commit);
 		if (parents && parents->next) {
 			struct commit_list *p;
-			for (p = parents; p; p = p->next)
+			for (p = parents; p; p = p->next) {
+				if (p == parents)
+					t_first -= getnanotime();
+				else
+					t_nth -= getnanotime();
 				propagate_follow_pathspec_to_parent(opt, commit,
 								    p->item);
+				if (p == parents)
+					t_first += getnanotime();
+				else
+					t_nth += getnanotime();
+			}
 		} else if (parents) {
 			remember_follow_pathspec(opt, parents->item,
 				pathspec_single_path(&opt->diffopt.pathspec));

      reply	other threads:[~2026-10-13 21:25 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-13 13:55 Performance regression in `git log --follow` Patrick Steinhardt
2026-10-13 21:24 ` SZEDER Gábor [this message]

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=as6hmUG5AaL5iLbO@szeder.dev \
    --to=szeder.dev@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=ps@pks.im \
    --cc=vmiklos@collabora.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox