diff mbox series

unpack-trees: correctly compute result count

Message ID pull.520.git.1578621570180.gitgitgadget@gmail.com (mailing list archive)
State New, archived
Headers show
Series unpack-trees: correctly compute result count | expand

Commit Message

Linus Arver via GitGitGadget Jan. 10, 2020, 1:59 a.m. UTC
The clear_ce_flags_dir() method processes the cache entries within
a common directory. The returned int is the number of cache entries
processed by that directory. When using the sparse-checkout feature
in cone mode, we can skip the pattern matching for entries in the
directories that are entirely included or entirely excluded.

eb42feca (unpack-trees: hash less in cone mode, 2019-11-21)
introduced this performance feature. The old mechanism relied on
the counts returned by calling clear_ce_flags_1(), but the new
mechanism calculated the number of rows by subtracting "cache_end"
from "cache" to find the size of the range. However, the equation
is wrong because it divides by sizeof(struct cache_entry *). This
is not how pointer arithmetic works!

A coverity build of Git for Windows in preparation for the 2.25.0
release found this issue with the warning, "Pointer differences,
such as cache_end - cache, are automatically scaled down by the
size (8 bytes) of the pointed-to type (struct cache_entry *).
Most likely, the division by sizeof(struct cache_entry *) is
extraneous and should be eliminated." This warning is correct.

This leaves us with the question "how did this even work?" The
problem that occurs with this incorrect pointer arithmetic is
a performance-only bug, and a very slight one at that. Since
the entry count returned by clear_ce_flags_dir() is reduced by
a factor of 8, the loop in clear_ce_flags_1() will re-process
entries from those directories.

By inserting global counters into unpack-tree.c and tracing
them with trace2_data_intmax() (in a private change, for
testing), I was able to see count how many times the loop inside
clear_ce_flags_1() processed an entry and how many times
clear_ce_flags_dir() was called. Each of these are reduced by at
least a factor of 8 with the current change. A factor larger
than 8 happens when multiple levels of directories are repeated.

Specifically, in the Linux kernel repo, the command

	git sparse-checkout set LICENSES

restricts the working directory to only the files at root and
in the LICENSES directory. Here are the measured counts:

clear_ce_flags_1 loop blocks:
	Before: 11,520
	After:   1,621

clear_ce_flags_dir calls:
	Before: 7,048
	After:    606

While these are dramatic counts, the time spent in
clear_ce_flags_1() is under one millisecond in each case, so
the improvement is not measurable as an end-to-end time.

Reported-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
---
    unpack-trees: correctly compute result count
    
    Here is a very small fix to the cone-mode pattern-matching in
    unpack-trees.c. Johannes found this while running a Coverity scan for
    other issues. He alerted me to the problem and I could immediately see 
    my error here. In creating this patch, most of my time was spent asking
    "how did this work before?" and "why didn't this hurt performance?"
    Hopefully my commit message explains this thoroughly enough.
    
    As for making it into the release, I don't know. The change is small, it
    has a very limited scope, but this flaw is also not really hurting
    anything in a major way.
    
    Thanks, -Stolee

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-520%2Fderrickstolee%2Funpack-trees-division-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-520/derrickstolee/unpack-trees-division-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/520

 unpack-trees.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)


base-commit: 7a6a90c6ec48fc78c83d7090d6c1b95d8f3739c0

Comments

Jeff King Jan. 10, 2020, 10:37 a.m. UTC | #1
On Fri, Jan 10, 2020 at 01:59:30AM +0000, Derrick Stolee via GitGitGadget wrote:

>     Here is a very small fix to the cone-mode pattern-matching in
>     unpack-trees.c. Johannes found this while running a Coverity scan for
>     other issues. He alerted me to the problem and I could immediately see 
>     my error here. In creating this patch, most of my time was spent asking
>     "how did this work before?" and "why didn't this hurt performance?"
>     Hopefully my commit message explains this thoroughly enough.

Yes, it makes perfect sense (and as soon as I saw the explanation of the
problem, my immediate response was also "wait, how did this even work").

And the patch itself looks good.

>     As for making it into the release, I don't know. The change is small, it
>     has a very limited scope, but this flaw is also not really hurting
>     anything in a major way.

I could go either way.

This counts as something small and obvious enough that I'd consider
slipping it in at the last minute if it were fixing a bad bug. But given
how minor the bug is, being conservative makes sense to me, if only
because it's good to exercise our release discipline muscles. :)

-Peff
Derrick Stolee Jan. 10, 2020, 11:24 a.m. UTC | #2
On 1/10/2020 5:37 AM, Jeff King wrote:
> On Fri, Jan 10, 2020 at 01:59:30AM +0000, Derrick Stolee via GitGitGadget wrote:
>>
>>     As for making it into the release, I don't know. The change is small, it
>>     has a very limited scope, but this flaw is also not really hurting
>>     anything in a major way.
> 
> I could go either way.
> 
> This counts as something small and obvious enough that I'd consider
> slipping it in at the last minute if it were fixing a bad bug. But given
> how minor the bug is, being conservative makes sense to me, if only
> because it's good to exercise our release discipline muscles. :)

Perhaps this should be an example for future release windows.

Thanks,
-Stolee
Derrick Stolee Jan. 10, 2020, 11:30 a.m. UTC | #3
On 1/10/2020 6:24 AM, Derrick Stolee wrote:
> On 1/10/2020 5:37 AM, Jeff King wrote:
>> On Fri, Jan 10, 2020 at 01:59:30AM +0000, Derrick Stolee via GitGitGadget wrote:
>>>
>>>     As for making it into the release, I don't know. The change is small, it
>>>     has a very limited scope, but this flaw is also not really hurting
>>>     anything in a major way.
>>
>> I could go either way.
>>
>> This counts as something small and obvious enough that I'd consider
>> slipping it in at the last minute if it were fixing a bad bug. But given
>> how minor the bug is, being conservative makes sense to me, if only
>> because it's good to exercise our release discipline muscles. :)
> 
> Perhaps this should be an example for future release windows.

(Forgive me for fat-fingering and accidentally using "gmx.net" instead
of "gmx.de" for Johannes' email address. Fixed in this message.)

-Stolee
Junio C Hamano Jan. 10, 2020, 7:33 p.m. UTC | #4
Jeff King <peff@peff.net> writes:

> On Fri, Jan 10, 2020 at 01:59:30AM +0000, Derrick Stolee via GitGitGadget wrote:
>
>>     Here is a very small fix to the cone-mode pattern-matching in
>>     unpack-trees.c. Johannes found this while running a Coverity scan for
>>     other issues. He alerted me to the problem and I could immediately see 
>>     my error here. In creating this patch, most of my time was spent asking
>>     "how did this work before?" and "why didn't this hurt performance?"
>>     Hopefully my commit message explains this thoroughly enough.
>
> Yes, it makes perfect sense (and as soon as I saw the explanation of the
> problem, my immediate response was also "wait, how did this even work").
>
> And the patch itself looks good.
>
>>     As for making it into the release, I don't know. The change is small, it
>>     has a very limited scope, but this flaw is also not really hurting
>>     anything in a major way.
>
> I could go either way.
>
> This counts as something small and obvious enough that I'd consider
> slipping it in at the last minute if it were fixing a bad bug. But given
> how minor the bug is, being conservative makes sense to me, if only
> because it's good to exercise our release discipline muscles. :)

Heh.

On one hand, it is obvious, even to a mindless compiler, that the
author meant to count how many elements of cache[] array have been
processed in the loop, so it is clear that the patch makes the code
reflect the author's original intention better.

On the other hand, the code that reflects the author's original
intention has never been tested in the field---it could be possible
that the author thought cache[0] thru cache_end[0] have been
processed, but for some subtlety, only a very early part of the
range was correctly processed, and returning smaller range may have
been hiding that subtle bug ;-)

I do not see any such subtle bug in this particular case, so I am
somewhat tempted to say "it is clear that the 'fix' makes the code
do what the author wanted to do, *and* what the author wanted to do
seems sane, so let's apply it".

Having said that, the earlier part of the patch, i.e.

 	if (pl->use_cone_patterns && orig_ret == MATCHED_RECURSIVE) {
 		struct cache_entry **ce = cache;
-		rc = (cache_end - cache) / sizeof(struct cache_entry *);
+		rc = cache_end - cache;
 
 		while (ce < cache_end) {
 			(*ce)->ce_flags &= ~clear_mask;
 			ce++;
 		}

wouldn't have been needed any fix if it were actually *counting*,
which is what clear_ce_flags_dir() promises its callers it does,
instead of cheating with a subtraction. i.e.

	if (... RECURSIVE) {
		rc = 0;
		while (ce < cache_end) {
			clear;
			ce++;
			rc++;
		}
	}

and that may have been more future-proof way, in that the body of
the while loop may in the future decide to leave early etc. and
actually counting how far the processing progressed would be less
error prone.

ALso, the computation of cache_end earlier in the function looks
suspiciously similar to the code Emily recently touched to avoid
segfaulting against inconsistent index state, where it used
the entry count in the cache_tree structure (when it is valid) to
determine how many entries to skip over.  We may want to see if we
can apply the same optimization here.

Thanks.  Will queue but will not merge to 'master' ;-)
diff mbox series

Patch

diff --git a/unpack-trees.c b/unpack-trees.c
index 2399b6818b..3dba7f9f09 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1305,14 +1305,14 @@  static int clear_ce_flags_dir(struct index_state *istate,
 
 	if (pl->use_cone_patterns && orig_ret == MATCHED_RECURSIVE) {
 		struct cache_entry **ce = cache;
-		rc = (cache_end - cache) / sizeof(struct cache_entry *);
+		rc = cache_end - cache;
 
 		while (ce < cache_end) {
 			(*ce)->ce_flags &= ~clear_mask;
 			ce++;
 		}
 	} else if (pl->use_cone_patterns && orig_ret == NOT_MATCHED) {
-		rc = (cache_end - cache) / sizeof(struct cache_entry *);
+		rc = cache_end - cache;
 	} else {
 		rc = clear_ce_flags_1(istate, cache, cache_end - cache,
 				      prefix,