Skip to content

Commit 9a41448

Browse files
peffgitster
authored andcommitted
lookup_object: prioritize recently found objects
The lookup_object function is backed by a hash table of all objects we have seen in the program. We manage collisions with a linear walk over the colliding entries, checking each with hashcmp(). The main cost of lookup is in these hashcmp() calls; finding our item in the first slot is cheaper than finding it in the second slot, which is cheaper than the third, and so on. If we assume that there is some locality to the object lookups (e.g., if X and Y collide, and we have just looked up X, the next lookup is more likely to be for X than for Y), then we can improve our average lookup speed by checking X before Y. This patch does so by swapping a found item to the front of the collision chain. The p0001 perf test reveals that this does indeed exploit locality in the case of "rev-list --all --objects": Test origin this tree ------------------------------------------------------------------------- 0001.1: rev-list --all 0.40(0.38+0.02) 0.40(0.36+0.03) +0.0% 0001.2: rev-list --all --objects 2.24(2.17+0.05) 1.86(1.79+0.05) -17.0% This is not surprising, as the full object traversal will hit the same tree entries over and over (e.g., for every commit that doesn't change "Documentation/", we will have to look up the same sha1 just to find out that we already processed it). The reason why this technique works (and does not violate any properties of the hash table) is subtle and bears some explanation. Let's imagine we get a lookup for sha1 `X`, and it hashes to bucket `i` in our table. That stretch of the table may look like: index | i-1 | i | i+1 | i+2 | ----------------------------------- entry ... | A | B | C | X | ... ----------------------------------- We start our probe at i, see that B does not match, nor does C, and finally find X. There may be multiple C's in the middle, but we know that there are no empty slots (or else we would not find X at all). We do not know the original index of B; it may be `i`, or it may be less than i (e.g., if it were `i-1`, it would collide with A and spill over into the `i` bucket). So it is acceptable for us to move it to the right of a contiguous stretch of entries (because we will find it from a linear walk starting anywhere at `i` or before), but never to the left (if we moved it to `i-1`, we would miss it when starting our walk at `i`). We do know the original index of X; it is `i`, so it is safe to place it anywhere in the contiguous stretch between `i` and where we found it (`i+2` in the this case). This patch does a pure swap; after finding X in the situation above, we would end with: index | i-1 | i | i+1 | i+2 | ----------------------------------- entry ... | A | X | C | B | ... ----------------------------------- We could instead bump X into the `i` slot, and then shift the whole contiguous chain down by one, resulting in: index | i-1 | i | i+1 | i+2 | ----------------------------------- entry ... | A | X | B | C | ... ----------------------------------- That puts our chain in true most-recently-used order. However, experiments show that it is not any faster (and in fact, is slightly slower due to the extra manipulation). Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1599999 commit 9a41448

File tree

1 file changed

+12
-2
lines changed

1 file changed

+12
-2
lines changed

object.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,20 +71,30 @@ static unsigned int hashtable_index(const unsigned char *sha1)
7171

7272
struct object *lookup_object(const unsigned char *sha1)
7373
{
74-
unsigned int i;
74+
unsigned int i, first;
7575
struct object *obj;
7676

7777
if (!obj_hash)
7878
return NULL;
7979

80-
i = hashtable_index(sha1);
80+
first = i = hashtable_index(sha1);
8181
while ((obj = obj_hash[i]) != NULL) {
8282
if (!hashcmp(sha1, obj->sha1))
8383
break;
8484
i++;
8585
if (i == obj_hash_size)
8686
i = 0;
8787
}
88+
if (obj && i != first) {
89+
/*
90+
* Move object to where we started to look for it so
91+
* that we do not need to walk the hash table the next
92+
* time we look for it.
93+
*/
94+
struct object *tmp = obj_hash[i];
95+
obj_hash[i] = obj_hash[first];
96+
obj_hash[first] = tmp;
97+
}
8898
return obj;
8999
}
90100

0 commit comments

Comments
 (0)