All of lore.kernel.org
 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 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.