Skip to content

Commit 4c6c797

Browse files
Derrick Stolee via GitGitGadgetgitster
Derrick Stolee via GitGitGadget
authored andcommitted
unpack-trees: correctly compute result count
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. eb42fec (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 <[email protected]> Signed-off-by: Derrick Stolee <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 757ff35 commit 4c6c797

File tree

1 file changed

+2
-2
lines changed

1 file changed

+2
-2
lines changed

unpack-trees.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,14 +1305,14 @@ static int clear_ce_flags_dir(struct index_state *istate,
13051305

13061306
if (pl->use_cone_patterns && orig_ret == MATCHED_RECURSIVE) {
13071307
struct cache_entry **ce = cache;
1308-
rc = (cache_end - cache) / sizeof(struct cache_entry *);
1308+
rc = cache_end - cache;
13091309

13101310
while (ce < cache_end) {
13111311
(*ce)->ce_flags &= ~clear_mask;
13121312
ce++;
13131313
}
13141314
} else if (pl->use_cone_patterns && orig_ret == NOT_MATCHED) {
1315-
rc = (cache_end - cache) / sizeof(struct cache_entry *);
1315+
rc = cache_end - cache;
13161316
} else {
13171317
rc = clear_ce_flags_1(istate, cache, cache_end - cache,
13181318
prefix,

0 commit comments

Comments
 (0)