git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v4 08/10] commit: add short-circuit to paint_down_to_common()

From
Derrick Stolee <stolee@gmail.com>
Date
May 1, 2018, 11:47 UTC
Message-ID
<3e3ae7e4-9efd-3046-c0c2-7b3bf12d0d81@gmail.com>
In-Reply-To
<864ljsgwx1.fsf@gmail.com>
On 4/30/2018 6:19 PM, Jakub Narebski wrote:
Show 84 quoted lines
> Derrick Stolee <dstolee@microsoft.com> writes:
>
>> When running 'git branch --contains', the in_merge_bases_many()
>> method calls paint_down_to_common() to discover if a specific
>> commit is reachable from a set of branches. Commits with lower
>> generation number are not needed to correctly answer the
>> containment query of in_merge_bases_many().
>>
>> Add a new parameter, min_generation, to paint_down_to_common() that
>> prevents walking commits with generation number strictly less than
>> min_generation. If 0 is given, then there is no functional change.
> This is thanks to the fact that generation numbers start at zero (as
> special case, though, with _ZERO), and we use strict inequality to avoid
> handling _ZERO etc. in a special way.
>
> As you wrote in response in previous version of this series, because
> paint_down_to_common() is file-local, there is no need to come up with
> symbolic name for GENERATION_NO_CUTOFF case.
>
> All right.
>
>> For in_merge_bases_many(), we can pass commit->generation as the
>> cutoff, and this saves time during 'git branch --contains' queries
>> that would otherwise walk "around" the commit we are inspecting.
> All right, and when using paint_down_to_common() to actually find merge
> bases, and not only check for containment, we cannot use cutoff.
> Therefore at least one call site needs to run it without functional
> change... which we can do.  Good.
>
>> For a copy of the Linux repository, where HEAD is checked out at
>> v4.13~100, we get the following performance improvement for
>> 'git branch --contains' over the previous commit:
>>
>> Before: 0.21s
>> After:  0.13s
>> Rel %: -38%
> Nice.
>
>> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
>> ---
>>   commit.c | 18 ++++++++++++++----
>>   1 file changed, 14 insertions(+), 4 deletions(-)
> Let me reorder chunks a bit to make it easier to review.
>
>> diff --git a/commit.c b/commit.c
>> index 7bb007f56a..e2e16ea1a7 100644
>> --- a/commit.c
>> +++ b/commit.c
>> @@ -1070,7 +1080,7 @@ int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit *
>>   	if (commit->generation > min_generation)
>>   		return ret;
>>   
>> -	bases = paint_down_to_common(commit, nr_reference, reference);
>> +	bases = paint_down_to_common(commit, nr_reference, reference, commit->generation);
>>   	if (commit->object.flags & PARENT2)
>>   		ret = 1;
>>   	clear_commit_marks(commit, all_flags);
>> @@ -808,11 +808,14 @@ static int queue_has_nonstale(struct prio_queue *queue)
>>   }
>>   
>>   /* all input commits in one and twos[] must have been parsed! */
>> -static struct commit_list *paint_down_to_common(struct commit *one, int n, struct commit **twos)
>> +static struct commit_list *paint_down_to_common(struct commit *one, int n,
>> +						struct commit **twos,
>> +						int min_generation)
>>   {
>>   	struct prio_queue queue = { compare_commits_by_gen_then_commit_date };
>>   	struct commit_list *result = NULL;
>>   	int i;
>> +	uint32_t last_gen = GENERATION_NUMBER_INFINITY;
>>   
>>   	one->object.flags |= PARENT1;
>>   	if (!n) {
>> @@ -831,6 +834,13 @@ static struct commit_list *paint_down_to_common(struct commit *one, int n, struc
>>   		struct commit_list *parents;
>>   		int flags;
>>   
>> +		if (commit->generation > last_gen)
>> +			BUG("bad generation skip");
>> +		last_gen = commit->generation;
> Shouldn't we provide more information about where the problem is to the
> user, to make it easier to debug the repository / commit-graph data?
>
> Good to have this sanity check here.

This BUG() _should_ only be seen by developers who add callers which do not load commits from the commit-graph file. There is a chance that there are cases not covered by this patch and the added tests, though. Hopefully we catch them all by dogfooding the feature before turning it on by default.

I can add the following to help debug these bad situations:

+ BUG("bad generation skip %d > %d at %s", + commit->generation, last_gen, + oid_to_hex(&commit->object.oid));

Show 57 quoted lines
>
>> +
>> +		if (commit->generation < min_generation)
>> +			break;
> So the reasoning for this, as far as I understand, is the following.
> Please correct me if I am wrong.
>
> The callsite with non-zero min_generation, in_merge_bases_many(), tries
> to find out if "commit" is an ancestor of one of the "references".  At
> least one of "references" is above "commit", so in_merge_bases_many()
> uses paint_down_to_common() - but is interested only if "commit" was
> painted as reachable from one of "references".
>
> Thus we can interrupt the walk if we know that none of [considered]
> commits in the queue can reach "commit"/"one" - as if they were all
> STALE.
>
> The search is done using priority queue (a bit like in Dijkstra
> algorithm), with newer commits - with larger generation numbers -
> considered first.  Thus if current commit has generation number less
> than min_generation cutoff, i.e. if it is below "commit", then all
> remaining commits in the queue are below cutoff.
>
> Good.
>
>> +
>>   		flags = commit->object.flags & (PARENT1 | PARENT2 | STALE);
>>   		if (flags == (PARENT1 | PARENT2)) {
>>   			if (!(commit->object.flags & RESULT)) {
>> @@ -879,7 +889,7 @@ static struct commit_list *merge_bases_many(struct commit *one, int n, struct co
>>   			return NULL;
>>   	}
>>   
>> -	list = paint_down_to_common(one, n, twos);
>> +	list = paint_down_to_common(one, n, twos, 0);
> When calculating merge bases there is no such possibility of an early
> return due to generation number cutoff.  All right then.
>
>>   
>>   	while (list) {
>>   		struct commit *commit = pop_commit(&list);
>> @@ -946,7 +956,7 @@ static int remove_redundant(struct commit **array, int cnt)
>>   			filled_index[filled] = j;
>>   			work[filled++] = array[j];
>>   		}
>> -		common = paint_down_to_common(array[i], filled, work);
>> +		common = paint_down_to_common(array[i], filled, work, 0);
> Here we are interested not only if "one"/array[i] is reachable from
> "twos"/work, but also if "twos" is reachable from "one".  Simple cutoff
> only works in one way, though I wonder if we couldn't use cutoff being
> minimum generation number of "one" and "twos" together.
>
> But that may be left for a separate commit (after checking that the
> above is correct).
>
> Not as simple and obvious as paint_down_to_common() used in
> in_merge_bases_any(), so it is all right.

Thanks for reporting this. Since we are only concerned about reachability in this method, it is a good candidate to use min_generation. It is also subtle enough that we should leave it as a separate commit. Also, we can measure performance improvements separately, as I will mention in my commit message (but I'll copy it here):

     For a copy of the Linux repository, we measured the following
     performance improvements:
     git merge-base v3.3 v4.5
     Before: 234 ms
      After: 208 ms
      Rel %: -11%
     git merge-base v4.3 v4.5
     Before: 102 ms
      After:  83 ms
      Rel %: -19%
     The experiments above were chosen to demonstrate that we are
     improving the filtering of the merge-base set. In the first
     example, more time is spent walking the history to find the
     set of merge bases before the remove_redundant() call. The
     starting commits are closer together in the second example,
     therefore more time is spent in remove_redundant(). The relative
     change in performance differs as expected.
>
>>   		if (array[i]->object.flags & PARENT2)
>>   			redundant[i] = 1;
>>   		for (j = 0; j < filled; j++)
Previous: Jakub NarebskiNext: Jakub Narebski
Message 130 of 162 in “Compute and consume generation numbers”
  1. 0/6 Compute and consume generation numbersDerrick Stolee, Apr 3, 2018
  2. 1/6 object.c: parse commit in graph firstDerrick Stolee, Apr 3, 2018
  3. Jonathan TanApr 3, 2018
  4. Jeff KingApr 3, 2018
  5. Derrick StoleeApr 3, 2018
  6. 6/6 commit-graph.txt: update future workDerrick Stolee, Apr 3, 2018
  7. Jonathan TanApr 3, 2018
  8. 5/6 commit.c: use generation to halt paint walkDerrick Stolee, Apr 3, 2018
  9. Jonathan TanApr 3, 2018
  10. 4/6 commit: use generations in paint_down_to_common()Derrick Stolee, Apr 3, 2018
  11. Stefan BellerApr 3, 2018
  12. Jonathan TanApr 3, 2018
  13. 2/6 commit: add generation number to struct commmitDerrick Stolee, Apr 3, 2018
  14. Brandon WilliamsApr 3, 2018
  15. Jeff KingApr 3, 2018
  16. Derrick StoleeApr 3, 2018
  17. Brandon WilliamsApr 3, 2018
  18. Stefan BellerApr 3, 2018
  19. Ramsay JonesApr 3, 2018
  20. Jeff KingApr 3, 2018
  21. Jonathan TanApr 3, 2018
  22. 3/6 commit-graph: compute generation numbersDerrick Stolee, Apr 3, 2018
  23. Jonathan TanApr 3, 2018
  24. Stefan BellerApr 3, 2018
  25. Derrick StoleeApr 3, 2018
  26. Brandon WilliamsApr 3, 2018
  27. Derrick StoleeApr 3, 2018
  28. Jeff KingApr 3, 2018
  29. Jeff KingApr 3, 2018
  30. 7/6 ref-filter: use generation number for --containsDerrick Stolee, Apr 4, 2018
  31. 8/6 commit: use generation numbers for in_merge_bases()Derrick Stolee, Apr 4, 2018
  32. Derrick StoleeApr 4, 2018
  33. Brandon WilliamsApr 4, 2018
  34. Jeff KingApr 4, 2018
  35. Derrick StoleeApr 4, 2018
  36. Jeff KingApr 4, 2018
  37. Jeff KingApr 4, 2018
  38. Derrick StoleeApr 4, 2018
  39. Jeff KingApr 4, 2018
  40. Derrick StoleeApr 4, 2018
  41. Jeff KingApr 4, 2018
  42. Derrick StoleeApr 4, 2018
  43. Jeff KingApr 4, 2018
  44. Jakub NarebskiApr 7, 2018
  45. Jakub NarebskiApr 7, 2018
  46. Derrick StoleeApr 8, 2018
  47. Jakub NarebskiApr 11, 2018
  48. Derrick StoleeApr 11, 2018
  49. Jakub NarebskiApr 14, 2018
  50. Jakub NarebskiApr 21, 2018
  51. Derrick StoleeApr 23, 2018
  52. 00/10 Compute and consume generation numbersDerrick Stolee, Apr 9, 2018
  53. 01/10 object.c: parse commit in graph firstDerrick Stolee, Apr 9, 2018
  54. 02/10 merge: check config before loading commitsDerrick Stolee, Apr 9, 2018
  55. Junio C HamanoApr 11, 2018
  56. Derrick StoleeApr 11, 2018
  57. 03/10 commit: add generation number to struct commmitDerrick Stolee, Apr 9, 2018
  58. Stefan BellerApr 9, 2018
  59. Junio C HamanoApr 11, 2018
  60. Derrick StoleeApr 11, 2018
  61. Junio C HamanoApr 11, 2018
  62. 04/10 commit-graph: compute generation numbersDerrick Stolee, Apr 9, 2018
  63. Junio C HamanoApr 11, 2018
  64. Derrick StoleeApr 11, 2018
  65. Eric SunshineApr 11, 2018
  66. Stefan BellerApr 11, 2018
  67. 05/10 commit: use generations in paint_down_to_common()Derrick Stolee, Apr 9, 2018
  68. 06/10 commit.c: use generation to halt paint walkDerrick Stolee, Apr 9, 2018
  69. Junio C HamanoApr 11, 2018
  70. Derrick StoleeApr 11, 2018
  71. 07/10 commit-graph.txt: update future workDerrick Stolee, Apr 9, 2018
  72. Junio C HamanoApr 12, 2018
  73. Derrick StoleeApr 12, 2018
  74. Jakub NarebskiApr 13, 2018
  75. 10/10 commit: add short-circuit to paint_down_to_common()Derrick Stolee, Apr 9, 2018
  76. 09/10 commit: use generation numbers for in_merge_bases()Derrick Stolee, Apr 9, 2018
  77. 08/10 ref-filter: use generation number for --containsDerrick Stolee, Apr 9, 2018
  78. 0/9 Compute and consume generation numbersDerrick Stolee, Apr 17, 2018
  79. 1/9 commit: add generation number to struct commmitDerrick Stolee, Apr 17, 2018
  80. 3/9 commit: use generations in paint_down_to_common()Derrick Stolee, Apr 17, 2018
  81. Jakub NarebskiApr 18, 2018
  82. Derrick StoleeApr 18, 2018
  83. 2/9 commit-graph: compute generation numbersDerrick Stolee, Apr 17, 2018
  84. 4/9 commit-graph.txt: update design documentDerrick Stolee, Apr 17, 2018
  85. Jakub NarebskiApr 18, 2018
  86. 6/9 commit: use generation numbers for in_merge_bases()Derrick Stolee, Apr 17, 2018
  87. Jakub NarebskiApr 18, 2018
  88. Derrick StoleeApr 23, 2018
  89. 7/9 commit: add short-circuit to paint_down_to_common()Derrick Stolee, Apr 17, 2018
  90. Jakub NarebskiApr 18, 2018
  91. Derrick StoleeApr 23, 2018
  92. Jakub NarebskiApr 23, 2018
  93. Derrick StoleeApr 24, 2018
  94. Jakub NarebskiApr 19, 2018
  95. 8/9 commit-graph: always load commit-graph informationDerrick Stolee, Apr 17, 2018
  96. Derrick StoleeApr 17, 2018
  97. Jakub NarebskiApr 19, 2018
  98. Derrick StoleeApr 23, 2018
  99. 9/9 merge: check config before loading commitsDerrick Stolee, Apr 17, 2018
  100. 5/9 ref-filter: use generation number for --containsDerrick Stolee, Apr 17, 2018
  101. Jakub NarebskiApr 18, 2018
  102. Derrick StoleeApr 23, 2018
  103. Jakub NarebskiApr 24, 2018
  104. Derrick StoleeApr 25, 2018
  105. Jakub NarebskiApr 19, 2018
  106. Derrick StoleeApr 23, 2018
  107. 00/10 Compute and consume generation numbersDerrick Stolee, Apr 25, 2018
  108. 01/10 ref-filter: fix outdated comment on in_commit_listDerrick Stolee, Apr 25, 2018
  109. Jakub NarebskiApr 28, 2018
  110. 02/10 commit: add generation number to struct commmitDerrick Stolee, Apr 25, 2018
  111. Jakub NarebskiApr 28, 2018
  112. Derrick StoleeApr 30, 2018
  113. 03/10 commit-graph: compute generation numbersDerrick Stolee, Apr 25, 2018
  114. Junio C HamanoApr 26, 2018
  115. Derrick StoleeApr 26, 2018
  116. Derrick StoleeApr 26, 2018
  117. Jakub NarebskiApr 29, 2018
  118. Derrick StoleeMay 1, 2018
  119. Jakub NarebskiMay 2, 2018
  120. 07/10 commit: use generation numbers for in_merge_bases()Derrick Stolee, Apr 25, 2018
  121. Jakub NarebskiApr 30, 2018
  122. 06/10 ref-filter: use generation number for --containsDerrick Stolee, Apr 25, 2018
  123. Jakub NarebskiApr 30, 2018
  124. 05/10 commit-graph: always load commit-graph informationDerrick Stolee, Apr 25, 2018
  125. Jakub NarebskiApr 29, 2018
  126. Derrick StoleeMay 1, 2018
  127. Jakub NarebskiApr 29, 2018
  128. 08/10 commit: add short-circuit to paint_down_to_common()Derrick Stolee, Apr 25, 2018
  129. Jakub NarebskiApr 30, 2018
  130. Derrick StoleeMay 1, 2018
  131. Jakub NarebskiMay 2, 2018
  132. Derrick StoleeMay 2, 2018
  133. 04/10 commit: use generations in paint_down_to_common()Derrick Stolee, Apr 25, 2018
  134. Junio C HamanoApr 26, 2018
  135. Jakub NarebskiApr 26, 2018
  136. Jakub NarebskiApr 28, 2018
  137. Jakub NarebskiApr 29, 2018
  138. 10/10 commit-graph.txt: update design documentDerrick Stolee, Apr 25, 2018
  139. Jakub NarebskiApr 30, 2018
  140. Derrick StoleeMay 1, 2018
  141. Jakub NarebskiMay 2, 2018
  142. 09/10 merge: check config before loading commitsDerrick Stolee, Apr 25, 2018
  143. Jakub NarebskiApr 30, 2018
  144. Derrick StoleeMay 1, 2018
  145. Jakub NarebskiMay 2, 2018
  146. Derrick StoleeApr 25, 2018
  147. Jakub NarebskiApr 28, 2018
  148. 00/11 Compute and consume generation numbersDerrick Stolee, May 1, 2018
  149. 01/11 ref-filter: fix outdated comment on in_commit_listDerrick Stolee, May 1, 2018
  150. 03/11 commit-graph: compute generation numbersDerrick Stolee, May 1, 2018
  151. 02/11 commit: add generation number to struct commmitDerrick Stolee, May 1, 2018
  152. 04/11 commit: use generations in paint_down_to_common()Derrick Stolee, May 1, 2018
  153. 05/11 commit-graph: always load commit-graph informationDerrick Stolee, May 1, 2018
  154. 07/11 commit: use generation numbers for in_merge_bases()Derrick Stolee, May 1, 2018
  155. 08/11 commit: add short-circuit to paint_down_to_common()Derrick Stolee, May 1, 2018
  156. 09/11 commit: use generation number in remove_redundant()Derrick Stolee, May 1, 2018
  157. Derrick StoleeMay 1, 2018
  158. Jakub NarebskiMay 3, 2018
  159. 10/11 merge: check config before loading commitsDerrick Stolee, May 1, 2018
  160. 11/11 commit-graph.txt: update design documentDerrick Stolee, May 1, 2018
  161. 06/11 ref-filter: use generation number for --containsDerrick Stolee, May 1, 2018
  162. Jakub NarebskiMay 3, 2018

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.