aboutsummaryrefslogtreecommitdiffstats
path: root/t/perf
diff options
context:
space:
mode:
authorRené Scharfe <l.s.r@web.de>2025-07-18 11:39:06 +0200
committerJunio C Hamano <gitster@pobox.com>2025-07-22 07:28:23 -0700
commitd6ec08788e667d4f556e9c2d97bbd7adb7e582be (patch)
tree833bcbff1f6ebaf69daac4faabd4e23969f7b064 /t/perf
parent16bd9f20a403117f2e0d9bcda6c6e621d3763e77 (diff)
downloadgit-d6ec08788e667d4f556e9c2d97bbd7adb7e582be.tar.gz
commit: convert pop_most_recent_commit() to prio_queue
pop_most_recent_commit() calls commit_list_insert_by_date() for parent commits, which is itself called in a loop. This can lead to quadratic complexity if there are many merges. Replace the commit_list with a prio_queue to ensure logarithmic worst case complexity and convert all three users. Add a performance test that exercises one of them using a pathological history that consists of 50% merges and 50% root commits to demonstrate the speedup: Test v2.50.1 HEAD ---------------------------------------------------------------------- 1501.2: rev-parse ':/65535' 2.48(2.47+0.00) 0.20(0.19+0.00) -91.9% Alas, sane histories don't benefit from the conversion much, and traversing Git's own history takes a 1% performance hit on my machine: $ hyperfine -w3 -L git ./git_2.50.1,./git '{git} rev-parse :/^Initial.revision' Benchmark 1: ./git_2.50.1 rev-parse :/^Initial.revision Time (mean ± σ): 1.071 s ± 0.004 s [User: 1.052 s, System: 0.017 s] Range (min … max): 1.067 s … 1.078 s 10 runs Benchmark 2: ./git rev-parse :/^Initial.revision Time (mean ± σ): 1.079 s ± 0.003 s [User: 1.060 s, System: 0.017 s] Range (min … max): 1.074 s … 1.083 s 10 runs Summary ./git_2.50.1 rev-parse :/^Initial.revision ran 1.01 ± 0.00 times faster than ./git rev-parse :/^Initial.revision 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/p1501-rev-parse-oneline.sh71
1 files changed, 71 insertions, 0 deletions
diff --git a/t/perf/p1501-rev-parse-oneline.sh b/t/perf/p1501-rev-parse-oneline.sh
new file mode 100755
index 0000000000..538fa9c404
--- /dev/null
+++ b/t/perf/p1501-rev-parse-oneline.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+test_description='Test :/ object name notation'
+
+. ./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
+}
+
+test_expect_success 'setup' '
+ build_history 16 | git fast-import &&
+ git log --format="%H %s" --reverse >commits &&
+ sed -n -e "s/ .*$//p" -e "q" <commits >expect &&
+ sed -n -e "s/^.* //p" -e "q" <commits >needle
+'
+
+test_perf "rev-parse :/$(cat needle)" '
+ git rev-parse :/$(cat needle) >actual
+'
+
+test_expect_success 'verify result' '
+ test_cmp expect actual
+'
+
+test_done