Skip to content

Commit 0f6e503

Browse files
committed
Merge branch 'rs/pop-recent-commit-with-prio-queue'
The pop_most_recent_commit() function can have quite expensive worst case performance characteristics, which has been optimized by using prio-queue data structure. * rs/pop-recent-commit-with-prio-queue: commit: use prio_queue_replace() in pop_most_recent_commit() prio-queue: add prio_queue_replace() commit: convert pop_most_recent_commit() to prio_queue
2 parents e4ef048 + a79e351 commit 0f6e503

File tree

10 files changed

+170
-34
lines changed

10 files changed

+170
-34
lines changed

commit.c

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "parse.h"
3232
#include "object-file.h"
3333
#include "object-file-convert.h"
34+
#include "prio-queue.h"
3435

3536
static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
3637

@@ -739,20 +740,27 @@ void commit_list_sort_by_date(struct commit_list **list)
739740
commit_list_sort(list, commit_list_compare_by_date);
740741
}
741742

742-
struct commit *pop_most_recent_commit(struct commit_list **list,
743+
struct commit *pop_most_recent_commit(struct prio_queue *queue,
743744
unsigned int mark)
744745
{
745-
struct commit *ret = pop_commit(list);
746+
struct commit *ret = prio_queue_peek(queue);
747+
int get_pending = 1;
746748
struct commit_list *parents = ret->parents;
747749

748750
while (parents) {
749751
struct commit *commit = parents->item;
750752
if (!repo_parse_commit(the_repository, commit) && !(commit->object.flags & mark)) {
751753
commit->object.flags |= mark;
752-
commit_list_insert_by_date(commit, list);
754+
if (get_pending)
755+
prio_queue_replace(queue, commit);
756+
else
757+
prio_queue_put(queue, commit);
758+
get_pending = 0;
753759
}
754760
parents = parents->next;
755761
}
762+
if (get_pending)
763+
prio_queue_get(queue);
756764
return ret;
757765
}
758766

commit.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,10 @@ const char *repo_logmsg_reencode(struct repository *r,
201201

202202
const char *skip_blank_lines(const char *msg);
203203

204-
/** Removes the first commit from a list sorted by date, and adds all
205-
* of its parents.
206-
**/
207-
struct commit *pop_most_recent_commit(struct commit_list **list,
204+
struct prio_queue;
205+
206+
/* Removes the first commit from a prio_queue and adds its parents. */
207+
struct commit *pop_most_recent_commit(struct prio_queue *queue,
208208
unsigned int mark);
209209

210210
struct commit *pop_commit(struct commit_list **stack);

fetch-pack.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "commit-graph.h"
3535
#include "sigchain.h"
3636
#include "mergesort.h"
37+
#include "prio-queue.h"
3738

3839
static int transfer_unpack_limit = -1;
3940
static int fetch_unpack_limit = -1;
@@ -601,15 +602,15 @@ static int find_common(struct fetch_negotiator *negotiator,
601602
return count ? retval : 0;
602603
}
603604

604-
static struct commit_list *complete;
605+
static struct prio_queue complete = { compare_commits_by_commit_date };
605606

606607
static int mark_complete(const struct object_id *oid)
607608
{
608609
struct commit *commit = deref_without_lazy_fetch(oid, 1);
609610

610611
if (commit && !(commit->object.flags & COMPLETE)) {
611612
commit->object.flags |= COMPLETE;
612-
commit_list_insert(commit, &complete);
613+
prio_queue_put(&complete, commit);
613614
}
614615
return 0;
615616
}
@@ -626,9 +627,12 @@ static int mark_complete_oid(const char *refname UNUSED,
626627
static void mark_recent_complete_commits(struct fetch_pack_args *args,
627628
timestamp_t cutoff)
628629
{
629-
while (complete && cutoff <= complete->item->date) {
630+
while (complete.nr) {
631+
struct commit *item = prio_queue_peek(&complete);
632+
if (item->date < cutoff)
633+
break;
630634
print_verbose(args, _("Marking %s as complete"),
631-
oid_to_hex(&complete->item->object.oid));
635+
oid_to_hex(&item->object.oid));
632636
pop_most_recent_commit(&complete, COMPLETE);
633637
}
634638
}
@@ -798,7 +802,6 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
798802
refs_for_each_rawref(get_main_ref_store(the_repository),
799803
mark_complete_oid, NULL);
800804
for_each_cached_alternate(NULL, mark_alternate_complete);
801-
commit_list_sort_by_date(&complete);
802805
if (cutoff)
803806
mark_recent_complete_commits(args, cutoff);
804807
}

object-name.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "commit-reach.h"
2929
#include "date.h"
3030
#include "object-file-convert.h"
31+
#include "prio-queue.h"
3132

3233
static int get_oid_oneline(struct repository *r, const char *, struct object_id *,
3334
const struct commit_list *);
@@ -1461,7 +1462,7 @@ static int get_oid_oneline(struct repository *r,
14611462
const char *prefix, struct object_id *oid,
14621463
const struct commit_list *list)
14631464
{
1464-
struct commit_list *copy = NULL, **copy_tail = &copy;
1465+
struct prio_queue copy = { compare_commits_by_commit_date };
14651466
const struct commit_list *l;
14661467
int found = 0;
14671468
int negative = 0;
@@ -1483,9 +1484,9 @@ static int get_oid_oneline(struct repository *r,
14831484

14841485
for (l = list; l; l = l->next) {
14851486
l->item->object.flags |= ONELINE_SEEN;
1486-
copy_tail = &commit_list_insert(l->item, copy_tail)->next;
1487+
prio_queue_put(&copy, l->item);
14871488
}
1488-
while (copy) {
1489+
while (copy.nr) {
14891490
const char *p, *buf;
14901491
struct commit *commit;
14911492
int matches;
@@ -1507,7 +1508,7 @@ static int get_oid_oneline(struct repository *r,
15071508
regfree(&regex);
15081509
for (l = list; l; l = l->next)
15091510
clear_commit_marks(l->item, ONELINE_SEEN);
1510-
free_commit_list(copy);
1511+
clear_prio_queue(&copy);
15111512
return found ? 0 : -1;
15121513
}
15131514

@@ -2061,7 +2062,6 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo,
20612062
cb.list = &list;
20622063
refs_for_each_ref(get_main_ref_store(repo), handle_one_ref, &cb);
20632064
refs_head_ref(get_main_ref_store(repo), handle_one_ref, &cb);
2064-
commit_list_sort_by_date(&list);
20652065
ret = get_oid_oneline(repo, name + 2, oid, list);
20662066

20672067
free_commit_list(list);

prio-queue.c

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,10 @@ void prio_queue_put(struct prio_queue *queue, void *thing)
5858
}
5959
}
6060

61-
void *prio_queue_get(struct prio_queue *queue)
61+
static void sift_down_root(struct prio_queue *queue)
6262
{
63-
void *result;
6463
size_t ix, child;
6564

66-
if (!queue->nr)
67-
return NULL;
68-
if (!queue->compare)
69-
return queue->array[--queue->nr].data; /* LIFO */
70-
71-
result = queue->array[0].data;
72-
if (!--queue->nr)
73-
return result;
74-
75-
queue->array[0] = queue->array[queue->nr];
76-
7765
/* Push down the one at the root */
7866
for (ix = 0; ix * 2 + 1 < queue->nr; ix = child) {
7967
child = ix * 2 + 1; /* left */
@@ -86,6 +74,23 @@ void *prio_queue_get(struct prio_queue *queue)
8674

8775
swap(queue, child, ix);
8876
}
77+
}
78+
79+
void *prio_queue_get(struct prio_queue *queue)
80+
{
81+
void *result;
82+
83+
if (!queue->nr)
84+
return NULL;
85+
if (!queue->compare)
86+
return queue->array[--queue->nr].data; /* LIFO */
87+
88+
result = queue->array[0].data;
89+
if (!--queue->nr)
90+
return result;
91+
92+
queue->array[0] = queue->array[queue->nr];
93+
sift_down_root(queue);
8994
return result;
9095
}
9196

@@ -97,3 +102,17 @@ void *prio_queue_peek(struct prio_queue *queue)
97102
return queue->array[queue->nr - 1].data;
98103
return queue->array[0].data;
99104
}
105+
106+
void prio_queue_replace(struct prio_queue *queue, void *thing)
107+
{
108+
if (!queue->nr) {
109+
prio_queue_put(queue, thing);
110+
} else if (!queue->compare) {
111+
queue->array[queue->nr - 1].ctr = queue->insertion_ctr++;
112+
queue->array[queue->nr - 1].data = thing;
113+
} else {
114+
queue->array[0].ctr = queue->insertion_ctr++;
115+
queue->array[0].data = thing;
116+
sift_down_root(queue);
117+
}
118+
}

prio-queue.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ void *prio_queue_get(struct prio_queue *);
5252
*/
5353
void *prio_queue_peek(struct prio_queue *);
5454

55+
/*
56+
* Replace the "thing" that compares the smallest with a new "thing",
57+
* like prio_queue_get()+prio_queue_put() would do, but in a more
58+
* efficient way. Does the same as prio_queue_put() if the queue is
59+
* empty.
60+
*/
61+
void prio_queue_replace(struct prio_queue *queue, void *thing);
62+
5563
void clear_prio_queue(struct prio_queue *);
5664

5765
/* Reverse the LIFO elements */

t/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,6 +1116,7 @@ benchmarks = [
11161116
'perf/p1450-fsck.sh',
11171117
'perf/p1451-fsck-skip-list.sh',
11181118
'perf/p1500-graph-walks.sh',
1119+
'perf/p1501-rev-parse-oneline.sh',
11191120
'perf/p2000-sparse-operations.sh',
11201121
'perf/p3400-rebase.sh',
11211122
'perf/p3404-rebase-interactive.sh',

t/perf/p1501-rev-parse-oneline.sh

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/bin/sh
2+
3+
test_description='Test :/ object name notation'
4+
5+
. ./perf-lib.sh
6+
7+
test_perf_fresh_repo
8+
9+
#
10+
# Creates lots of merges to make history traversal costly. In
11+
# particular it creates 2^($max_level-1)-1 2-way merges on top of
12+
# 2^($max_level-1) root commits. E.g., the commit history looks like
13+
# this for a $max_level of 3:
14+
#
15+
# _1_
16+
# / \
17+
# 2 3
18+
# / \ / \
19+
# 4 5 6 7
20+
#
21+
# The numbers are the fast-import marks, which also are the commit
22+
# messages. 1 is the HEAD commit and a merge, 2 and 3 are also merges,
23+
# 4-7 are the root commits.
24+
#
25+
build_history () {
26+
local max_level="$1" &&
27+
local level="${2:-1}" &&
28+
local mark="${3:-1}" &&
29+
if test $level -eq $max_level
30+
then
31+
echo "reset refs/heads/master" &&
32+
echo "from $ZERO_OID" &&
33+
echo "commit refs/heads/master" &&
34+
echo "mark :$mark" &&
35+
echo "committer C <[email protected]> 1234567890 +0000" &&
36+
echo "data <<EOF" &&
37+
echo "$mark" &&
38+
echo "EOF"
39+
else
40+
local level1=$((level+1)) &&
41+
local mark1=$((2*mark)) &&
42+
local mark2=$((2*mark+1)) &&
43+
build_history $max_level $level1 $mark1 &&
44+
build_history $max_level $level1 $mark2 &&
45+
echo "commit refs/heads/master" &&
46+
echo "mark :$mark" &&
47+
echo "committer C <[email protected]> 1234567890 +0000" &&
48+
echo "data <<EOF" &&
49+
echo "$mark" &&
50+
echo "EOF" &&
51+
echo "from :$mark1" &&
52+
echo "merge :$mark2"
53+
fi
54+
}
55+
56+
test_expect_success 'setup' '
57+
build_history 16 | git fast-import &&
58+
git log --format="%H %s" --reverse >commits &&
59+
sed -n -e "s/ .*$//p" -e "q" <commits >expect &&
60+
sed -n -e "s/^.* //p" -e "q" <commits >needle
61+
'
62+
63+
test_perf "rev-parse :/$(cat needle)" '
64+
git rev-parse :/$(cat needle) >actual
65+
'
66+
67+
test_expect_success 'verify result' '
68+
test_cmp expect actual
69+
'
70+
71+
test_done

t/unit-tests/u-prio-queue.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ static int intcmp(const void *va, const void *vb, void *data UNUSED)
1313
#define STACK -3
1414
#define GET -4
1515
#define REVERSE -5
16+
#define REPLACE -6
1617

1718
static int show(int *v)
1819
{
@@ -51,6 +52,15 @@ static void test_prio_queue(int *input, size_t input_size,
5152
case REVERSE:
5253
prio_queue_reverse(&pq);
5354
break;
55+
case REPLACE:
56+
peek = prio_queue_peek(&pq);
57+
cl_assert(i + 1 < input_size);
58+
cl_assert(input[i + 1] >= 0);
59+
cl_assert(j < result_size);
60+
cl_assert_equal_i(result[j], show(peek));
61+
j++;
62+
prio_queue_replace(&pq, &input[++i]);
63+
break;
5464
default:
5565
prio_queue_put(&pq, &input[i]);
5666
break;
@@ -81,6 +91,13 @@ void test_prio_queue__empty(void)
8191
((int []){ 1, 2, MISSING, 1, 2, MISSING }));
8292
}
8393

94+
void test_prio_queue__replace(void)
95+
{
96+
TEST_INPUT(((int []){ REPLACE, 6, 2, 4, REPLACE, 5, 7, GET,
97+
REPLACE, 1, DUMP }),
98+
((int []){ MISSING, 2, 4, 5, 1, 6, 7 }));
99+
}
100+
84101
void test_prio_queue__stack(void)
85102
{
86103
TEST_INPUT(((int []){ STACK, 8, 1, 5, 4, 6, 2, 3, DUMP }),
@@ -92,3 +109,9 @@ void test_prio_queue__reverse_stack(void)
92109
TEST_INPUT(((int []){ STACK, 1, 2, 3, 4, 5, 6, REVERSE, DUMP }),
93110
((int []){ 1, 2, 3, 4, 5, 6 }));
94111
}
112+
113+
void test_prio_queue__replace_stack(void)
114+
{
115+
TEST_INPUT(((int []){ STACK, 8, 1, 5, REPLACE, 4, 6, 2, 3, DUMP }),
116+
((int []){ 5, 3, 2, 6, 4, 1, 8 }));
117+
}

0 commit comments

Comments
 (0)