Skip to content

Commit 5ed9079

Browse files
committed
p6012: add perf test for merge-base with deep side branch
Add a perf test that benchmarks merge-base on a synthetic 500k-commit repository with a deep side branch. Includes variants with and without a commit-graph to cover the case where both tips are outside the commit-graph (INFINITY region). Signed-off-by: Kristofer Karlsson <krka@spotify.com>
1 parent 35227dd commit 5ed9079

1 file changed

Lines changed: 187 additions & 0 deletions

File tree

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
#!/bin/sh
2+
3+
test_description='Performance of merge-base with a deep side branch
4+
5+
Synthetic repository that triggers slow merge-base when a merge commit
6+
introduces a side branch rooted far back in history. The pathological
7+
case forces paint_down_to_common() to chase STALE flags through many
8+
intermediate commits after the merge-base is already found.
9+
10+
Topology (N = $NUM_COMMITS commits on main before the merge, F is
11+
the BRANCH_POINT-th of them, S = $SIDE_COMMITS commits on the side):
12+
13+
# F -- s1 -- ... -- sS (side, dates set old)
14+
# / \
15+
# 1 -- 2 -- ... -- N-1 -- N -- M (main)
16+
# |
17+
# P (pr branch)
18+
19+
merge-base(M, P) = N. Without early termination, the algorithm
20+
walks from N back through roughly N - BRANCH_POINT commits to F
21+
to exhaust STALE processing.
22+
'
23+
24+
. ./perf-lib.sh
25+
26+
NUM_COMMITS=500000
27+
BRANCH_POINT=10000
28+
SIDE_COMMITS=5
29+
EXTRA_COMMITS=10
30+
31+
test_expect_success 'setup synthetic repo' '
32+
git init --bare repo.git &&
33+
awk -v N=$NUM_COMMITS -v BP=$BRANCH_POINT -v S=$SIDE_COMMITS \
34+
-v E=$EXTRA_COMMITS '\''
35+
BEGIN {
36+
# Shared blob
37+
print "blob"
38+
print "mark :1"
39+
print "data 8"
40+
print "content"
41+
print ""
42+
43+
# Main branch commits: mark :2 is the 1st main
44+
# commit, mark :(N+1) is the Nth.
45+
for (i = 2; i <= N + 1; i++) {
46+
print "commit refs/heads/main"
47+
print "mark :" i
48+
print "committer C <c@c> " (1000000 + i) " +0000"
49+
print "data 2"
50+
print "x"
51+
if (i > 2)
52+
print "from :" (i - 1)
53+
print "M 100644 :1 file"
54+
print ""
55+
}
56+
57+
# Side branch forks from the BP-th main commit
58+
# (mark :(BP+1)), with old dates.
59+
side_start = BP + 1
60+
side_mark_base = N + 2
61+
for (j = 0; j < S; j++) {
62+
mark = side_mark_base + j
63+
if (j == 0)
64+
from_mark = side_start
65+
else
66+
from_mark = mark - 1
67+
print "commit refs/heads/side"
68+
print "mark :" mark
69+
print "committer C <c@c> " (500000 + j) " +0000"
70+
print "data 2"
71+
print "x"
72+
print "from :" from_mark
73+
print ""
74+
}
75+
76+
# Merge side into main
77+
main_tip = N + 1
78+
side_tip = side_mark_base + S - 1
79+
merge_mark = side_tip + 1
80+
print "commit refs/heads/main"
81+
print "mark :" merge_mark
82+
print "committer C <c@c> " (1000000 + N + 2) " +0000"
83+
print "data 2"
84+
print "x"
85+
print "from :" main_tip
86+
print "merge :" side_tip
87+
print ""
88+
89+
# PR branch forked from main tip (just before merge)
90+
pr_mark = merge_mark + 1
91+
print "commit refs/heads/pr"
92+
print "mark :" pr_mark
93+
print "committer C <c@c> " (1000000 + N + 3) " +0000"
94+
print "data 2"
95+
print "x"
96+
print "from :" main_tip
97+
print ""
98+
99+
# Save refs before extra commits for the
100+
# commit-graph boundary.
101+
print "reset refs/markers/graph-boundary-main"
102+
print "from :" merge_mark
103+
print ""
104+
print "reset refs/markers/graph-boundary-pr"
105+
print "from :" pr_mark
106+
print ""
107+
108+
# Extra commits on main (beyond commit-graph).
109+
next_mark = pr_mark + 1
110+
from_mark = merge_mark
111+
for (k = 0; k < E; k++) {
112+
print "commit refs/heads/main"
113+
print "mark :" next_mark
114+
print "committer C <c@c> " (2000000 + k) " +0000"
115+
print "data 2"
116+
print "x"
117+
print "from :" from_mark
118+
print ""
119+
from_mark = next_mark
120+
next_mark++
121+
}
122+
123+
# Extra commits on pr (beyond commit-graph).
124+
from_mark = pr_mark
125+
for (k = 0; k < E; k++) {
126+
print "commit refs/heads/pr"
127+
print "mark :" next_mark
128+
print "committer C <c@c> " (2000000 + k) " +0000"
129+
print "data 2"
130+
print "x"
131+
print "from :" from_mark
132+
print ""
133+
from_mark = next_mark
134+
next_mark++
135+
}
136+
}
137+
'\'' </dev/null |
138+
git -C repo.git fast-import --quiet &&
139+
140+
# Write commit-graph covering everything up to the boundary
141+
# markers, leaving the extra commits in the INFINITY region.
142+
git -C repo.git rev-list refs/markers/graph-boundary-main \
143+
refs/markers/graph-boundary-pr |
144+
git -C repo.git commit-graph write --stdin-commits &&
145+
146+
git -C repo.git rev-parse main~1~$EXTRA_COMMITS >expect
147+
'
148+
149+
test_expect_success 'merge-base result is correct' '
150+
git -C repo.git merge-base --all main pr >actual &&
151+
test_cmp expect actual
152+
'
153+
154+
test_perf 'merge-base: both tips in INFINITY region' '
155+
git -C repo.git merge-base --all main pr
156+
'
157+
158+
# Rewrite commit-graph to cover all commits, so both tips are in
159+
# the finite region.
160+
test_expect_success 'setup: full commit-graph' '
161+
git -C repo.git commit-graph write --reachable
162+
'
163+
164+
test_perf 'merge-base: both tips in finite region' '
165+
git -C repo.git merge-base --all main pr
166+
'
167+
168+
test_perf 'merge-base: no side branch (baseline)' '
169+
git -C repo.git merge-base --all main~1~$EXTRA_COMMITS pr
170+
'
171+
172+
# Remove commit-graph entirely: all 500k commits are INFINITY.
173+
test_expect_success 'setup: remove commit-graph' '
174+
rm -f repo.git/objects/info/commit-graph &&
175+
rm -rf repo.git/objects/info/commit-graphs
176+
'
177+
178+
test_perf 'merge-base: all INFINITY (no commit-graph)' '
179+
git -C repo.git merge-base --all main pr
180+
'
181+
182+
test_expect_success 'merge-base result correct without commit-graph' '
183+
git -C repo.git merge-base --all main pr >actual &&
184+
test_cmp expect actual
185+
'
186+
187+
test_done

0 commit comments

Comments
 (0)