aboutsummaryrefslogtreecommitdiffstats
path: root/t/perf
diff options
context:
space:
mode:
authorRené Scharfe <l.s.r@web.de>2025-10-24 18:47:10 +0200
committerJunio C Hamano <gitster@pobox.com>2025-10-24 10:13:17 -0700
commit134ec330d2945002d0ceb7de2ac6cd7ab0af762d (patch)
treedf8145a78d96e4eff0460dd674afaaed69b67834 /t/perf
parent81f86aacc4eb74cdb9c2c8082d36d2070c666045 (diff)
downloadgit-134ec330d2945002d0ceb7de2ac6cd7ab0af762d.tar.gz
commit-reach: avoid commit_list_insert_by_date()
Building a list using commit_list_insert_by_date() has quadratic worst case complexity. Avoid it by just appending in the loop and sorting at the end. The number of merge bases is usually small, so don't expect speedups in normal repositories. It has no limit, though. The added perf test shows a nice improvement when dealing with 16384 merge bases: Test v2.51.1 HEAD ----------------------------------------------------------------- 6010.2: git merge-base 0.55(0.54+0.00) 0.03(0.02+0.00) -94.5% Signed-off-by: René Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 't/perf')
-rwxr-xr-xt/perf/p6010-merge-base.sh101
1 files changed, 101 insertions, 0 deletions
diff --git a/t/perf/p6010-merge-base.sh b/t/perf/p6010-merge-base.sh
new file mode 100755
index 0000000000..54f52fa23e
--- /dev/null
+++ b/t/perf/p6010-merge-base.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+
+test_description='Test git merge-base'
+
+. ./perf-lib.sh
+
+test_perf_fresh_repo
+
+#
+# Creates lots of merges to make history traversal costly. In
+# particular it creates 2^($max_level-1)-1 2-way merges on top of
+# 2^($max_level-1) root commits. E.g., the commit history looks like
+# this for a $max_level of 3:
+#
+# _1_
+# / \
+# 2 3
+# / \ / \
+# 4 5 6 7
+#
+# The numbers are the fast-import marks, which also are the commit
+# messages. 1 is the HEAD commit and a merge, 2 and 3 are also merges,
+# 4-7 are the root commits.
+#
+build_history () {
+ local max_level="$1" &&
+ local level="${2:-1}" &&
+ local mark="${3:-1}" &&
+ if test $level -eq $max_level
+ then
+ echo "reset refs/heads/master" &&
+ echo "from $ZERO_OID" &&
+ echo "commit refs/heads/master" &&
+ echo "mark :$mark" &&
+ echo "committer C <c@example.com> 1234567890 +0000" &&
+ echo "data <<EOF" &&
+ echo "$mark" &&
+ echo "EOF"
+ else
+ local level1=$((level+1)) &&
+ local mark1=$((2*mark)) &&
+ local mark2=$((2*mark+1)) &&
+ build_history $max_level $level1 $mark1 &&
+ build_history $max_level $level1 $mark2 &&
+ echo "commit refs/heads/master" &&
+ echo "mark :$mark" &&
+ echo "committer C <c@example.com> 1234567890 +0000" &&
+ echo "data <<EOF" &&
+ echo "$mark" &&
+ echo "EOF" &&
+ echo "from :$mark1" &&
+ echo "merge :$mark2"
+ fi
+}
+
+#
+# Creates a new merge history in the same shape as build_history does,
+# while reusing the same root commits. This way the two top commits
+# have 2^($max_level-1) merge bases between them.
+#
+build_history2 () {
+ local max_level="$1" &&
+ local level="${2:-1}" &&
+ local mark="${3:-1}" &&
+ if test $level -lt $max_level
+ then
+ local level1=$((level+1)) &&
+ local mark1=$((2*mark)) &&
+ local mark2=$((2*mark+1)) &&
+ build_history2 $max_level $level1 $mark1 &&
+ build_history2 $max_level $level1 $mark2 &&
+ echo "commit refs/heads/master" &&
+ echo "mark :$mark" &&
+ echo "committer C <c@example.com> 1234567890 +0000" &&
+ echo "data <<EOF" &&
+ echo "$mark II" &&
+ echo "EOF" &&
+ echo "from :$mark1" &&
+ echo "merge :$mark2"
+ fi
+}
+
+test_expect_success 'setup' '
+ max_level=15 &&
+ build_history $max_level | git fast-import --export-marks=marks &&
+ git tag one &&
+ build_history2 $max_level | git fast-import --import-marks=marks --force &&
+ git tag two &&
+ git gc &&
+ git log --format=%H --no-merges >expect
+'
+
+test_perf 'git merge-base' '
+ git merge-base --all one two >actual
+'
+
+test_expect_success 'verify result' '
+ test_cmp expect actual
+'
+
+test_done