summaryrefslogtreecommitdiff
path: root/sha1_file.c
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2007-12-29 02:05:47 -0800
committerJunio C Hamano <gitster@pobox.com>2008-04-09 01:23:52 -0700
commit628522ec1439f414dcb1e71e300eb84a37ad1af9 (patch)
treecf431f5ae4e2eb3caa74cdfcb9eb78505ecd86a5 /sha1_file.c
parent2a5fe2545882721d6841bad11dae0f15b454bf0d (diff)
downloadgit-628522ec1439f414dcb1e71e300eb84a37ad1af9.tar.gz
sha1-lookup: more memory efficient search in sorted list of SHA-1
Currently, when looking for a packed object from the pack idx, a simple binary search is used. A conventional binary search loop looks like this: unsigned lo, hi; do { unsigned mi = (lo + hi) / 2; int cmp = "entry pointed at by mi" minus "target"; if (!cmp) return mi; "mi is the wanted one" if (cmp > 0) hi = mi; "mi is larger than target" else lo = mi+1; "mi is smaller than target" } while (lo < hi); "did not find what we wanted" The invariants are: - When entering the loop, 'lo' points at a slot that is never above the target (it could be at the target), 'hi' points at a slot that is guaranteed to be above the target (it can never be at the target). - We find a point 'mi' between 'lo' and 'hi' ('mi' could be the same as 'lo', but never can be as high as 'hi'), and check if 'mi' hits the target. There are three cases: - if it is a hit, we have found what we are looking for; - if it is strictly higher than the target, we set it to 'hi', and repeat the search. - if it is strictly lower than the target, we update 'lo' to one slot after it, because we allow 'lo' to be at the target and 'mi' is known to be below the target. If the loop exits, there is no matching entry. When choosing 'mi', we do not have to take the "middle" but anywhere in between 'lo' and 'hi', as long as lo <= mi < hi is satisfied. When we somehow know that the distance between the target and 'lo' is much shorter than the target and 'hi', we could pick 'mi' that is much closer to 'lo' than (hi+lo)/2, which a conventional binary search would pick. This patch takes advantage of the fact that the SHA-1 is a good hash function, and as long as there are enough entries in the table, we can expect uniform distribution. An entry that begins with for example "deadbeef..." is much likely to appear much later than in the midway of a reasonably populated table. In fact, it can be expected to be near 87% (222/256) from the top of the table. This is a work-in-progress and has switches to allow easier experiments and debugging. Exporting GIT_USE_LOOKUP environment variable enables this code. On my admittedly memory starved machine, with a partial KDE repository (3.0G pack with 95M idx): $ GIT_USE_LOOKUP=t git log -800 --stat HEAD >/dev/null 3.93user 0.16system 0:04.09elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+55588minor)pagefaults 0swaps Without the patch, the numbers are: $ git log -800 --stat HEAD >/dev/null 4.00user 0.15system 0:04.17elapsed 99%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+60258minor)pagefaults 0swaps In the same repository: $ GIT_USE_LOOKUP=t git log -2000 HEAD >/dev/null 0.12user 0.00system 0:00.12elapsed 97%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+4241minor)pagefaults 0swaps Without the patch, the numbers are: $ git log -2000 HEAD >/dev/null 0.05user 0.01system 0:00.07elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+0outputs (0major+8506minor)pagefaults 0swaps There isn't much time difference, but the number of minor faults seems to show that we are touching much smaller number of pages, which is expected. Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'sha1_file.c')
-rw-r--r--sha1_file.c35
1 files changed, 32 insertions, 3 deletions
diff --git a/sha1_file.c b/sha1_file.c
index 445a871db3..c2ab7ea11d 100644
--- a/sha1_file.c
+++ b/sha1_file.c
@@ -15,6 +15,7 @@
#include "tree.h"
#include "refs.h"
#include "pack-revindex.h"
+#include "sha1-lookup.h"
#ifndef O_NOATIME
#if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -1675,7 +1676,12 @@ off_t find_pack_entry_one(const unsigned char *sha1,
{
const uint32_t *level1_ofs = p->index_data;
const unsigned char *index = p->index_data;
- unsigned hi, lo;
+ unsigned hi, lo, stride;
+ static int use_lookup = -1;
+ static int debug_lookup = -1;
+
+ if (debug_lookup < 0)
+ debug_lookup = !!getenv("GIT_DEBUG_LOOKUP");
if (!index) {
if (open_pack_index(p))
@@ -1690,11 +1696,34 @@ off_t find_pack_entry_one(const unsigned char *sha1,
index += 4 * 256;
hi = ntohl(level1_ofs[*sha1]);
lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
+ if (p->index_version > 1) {
+ stride = 20;
+ } else {
+ stride = 24;
+ index += 4;
+ }
+
+ if (debug_lookup)
+ printf("%02x%02x%02x... lo %u hi %u nr %u\n",
+ sha1[0], sha1[1], sha1[2], lo, hi, p->num_objects);
+
+ if (use_lookup < 0)
+ use_lookup = !!getenv("GIT_USE_LOOKUP");
+ if (use_lookup) {
+ int pos = sha1_entry_pos(index, stride, 0,
+ lo, hi, p->num_objects, sha1);
+ if (pos < 0)
+ return 0;
+ return nth_packed_object_offset(p, pos);
+ }
do {
unsigned mi = (lo + hi) / 2;
- unsigned x = (p->index_version > 1) ? (mi * 20) : (mi * 24 + 4);
- int cmp = hashcmp(index + x, sha1);
+ int cmp = hashcmp(index + mi * stride, sha1);
+
+ if (debug_lookup)
+ printf("lo %u hi %u rg %u mi %u\n",
+ lo, hi, hi - lo, mi);
if (!cmp)
return nth_packed_object_offset(p, mi);
if (cmp > 0)