X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=diffcore-delta.c;h=e670f8512558c38d9a9d6e754cfc609b042b1195;hb=847d10f56d7853cd0e139a7c2e6ad0ad2de5c464;hp=d03787be65be82b0fcd6dc62dae77013b2576c80;hpb=c339e8e2cbfd35997ae4df6bc2dca84bddeb96e1;p=git.git diff --git a/diffcore-delta.c b/diffcore-delta.c index d03787be6..e670f8512 100644 --- a/diffcore-delta.c +++ b/diffcore-delta.c @@ -2,126 +2,222 @@ #include "diff.h" #include "diffcore.h" -struct linehash { - unsigned long bytes; - unsigned long hash; +/* + * Idea here is very simple. + * + * Almost all data we are interested in are text, but sometimes we have + * to deal with binary data. So we cut them into chunks delimited by + * LF byte, or 64-byte sequence, whichever comes first, and hash them. + * + * For those chunks, if the source buffer has more instances of it + * than the destination buffer, that means the difference are the + * number of bytes not copied from source to destination. If the + * counts are the same, everything was copied from source to + * destination. If the destination has more, everything was copied, + * and destination added more. + * + * We are doing an approximation so we do not really have to waste + * memory by actually storing the sequence. We just hash them into + * somewhere around 2^16 hashbuckets and count the occurrences. + */ + +/* Wild guess at the initial hash size */ +#define INITIAL_HASH_SIZE 9 + +/* We leave more room in smaller hash but do not let it + * grow to have unused hole too much. + */ +#define INITIAL_FREE(sz_log2) ((1<<(sz_log2))*(sz_log2-3)/(sz_log2)) + +/* A prime rather carefully chosen between 2^16..2^17, so that + * HASHBASE < INITIAL_FREE(17). We want to keep the maximum hashtable + * size under the current 2<<17 maximum, which can hold this many + * different values before overflowing to hashtable of size 2<<18. + */ +#define HASHBASE 107927 + +struct spanhash { + unsigned int hashval; + unsigned int cnt; +}; +struct spanhash_top { + int alloc_log2; + int free; + struct spanhash data[FLEX_ARRAY]; }; -static unsigned long hash_extended_line(const unsigned char **buf_p, - unsigned long left) +static struct spanhash_top *spanhash_rehash(struct spanhash_top *orig) { - /* An extended line is zero or more whitespace letters (including LF) - * followed by one non whitespace letter followed by zero or more - * non LF, and terminated with by a LF (or EOF). - */ - const unsigned char *bol = *buf_p; - const unsigned char *buf = bol; - unsigned long hashval = 0; - while (left) { - unsigned c = *buf++; - if (!c) - goto binary; - left--; - if (' ' < c) { - hashval = c; - break; + struct spanhash_top *new; + int i; + int osz = 1 << orig->alloc_log2; + int sz = osz << 1; + + new = xmalloc(sizeof(*orig) + sizeof(struct spanhash) * sz); + new->alloc_log2 = orig->alloc_log2 + 1; + new->free = INITIAL_FREE(new->alloc_log2); + memset(new->data, 0, sizeof(struct spanhash) * sz); + for (i = 0; i < osz; i++) { + struct spanhash *o = &(orig->data[i]); + int bucket; + if (!o->cnt) + continue; + bucket = o->hashval & (sz - 1); + while (1) { + struct spanhash *h = &(new->data[bucket++]); + if (!h->cnt) { + h->hashval = o->hashval; + h->cnt = o->cnt; + new->free--; + break; + } + if (sz <= bucket) + bucket = 0; } } - while (left) { - unsigned c = *buf++; - if (!c) - goto binary; - left--; - if (c == '\n') - break; - if (' ' < c) - hashval = hashval * 11 + c; - } - *buf_p = buf; - return hashval; - - binary: - *buf_p = NULL; - return 0; + free(orig); + return new; } -static int linehash_compare(const void *a_, const void *b_) +static struct spanhash_top *add_spanhash(struct spanhash_top *top, + unsigned int hashval, int cnt) { - struct linehash *a = (struct linehash *) a_; - struct linehash *b = (struct linehash *) b_; - if (a->hash < b->hash) return -1; - if (a->hash > b->hash) return 1; - return 0; -} + int bucket, lim; + struct spanhash *h; -static struct linehash *hash_lines(const unsigned char *buf, - unsigned long size) -{ - const unsigned char *eobuf = buf + size; - struct linehash *line = NULL; - int alloc = 0, used = 0; - - while (buf < eobuf) { - const unsigned char *ptr = buf; - unsigned long hash = hash_extended_line(&buf, eobuf-ptr); - if (!buf) { - free(line); - return NULL; + lim = (1 << top->alloc_log2); + bucket = hashval & (lim - 1); + while (1) { + h = &(top->data[bucket++]); + if (!h->cnt) { + h->hashval = hashval; + h->cnt = cnt; + top->free--; + if (top->free < 0) + return spanhash_rehash(top); + return top; } - if (alloc <= used) { - alloc = alloc_nr(alloc); - line = xrealloc(line, sizeof(*line) * alloc); + if (h->hashval == hashval) { + h->cnt += cnt; + return top; } - line[used].bytes = buf - ptr; - line[used].hash = hash; - used++; + if (lim <= bucket) + bucket = 0; } - qsort(line, used, sizeof(*line), linehash_compare); +} - /* Terminate the list */ - if (alloc <= used) - line = xrealloc(line, sizeof(*line) * (used+1)); - line[used].bytes = line[used].hash = 0; - return line; +static int spanhash_cmp(const void *a_, const void *b_) +{ + const struct spanhash *a = a_; + const struct spanhash *b = b_; + + /* A count of zero compares at the end.. */ + if (!a->cnt) + return !b->cnt ? 0 : 1; + if (!b->cnt) + return -1; + return a->hashval < b->hashval ? -1 : + a->hashval > b->hashval ? 1 : 0; } -int diffcore_count_changes(void *src, unsigned long src_size, - void *dst, unsigned long dst_size, +static struct spanhash_top *hash_chars(struct diff_filespec *one) +{ + int i, n; + unsigned int accum1, accum2, hashval; + struct spanhash_top *hash; + unsigned char *buf = one->data; + unsigned int sz = one->size; + int is_text = !diff_filespec_is_binary(one); + + i = INITIAL_HASH_SIZE; + hash = xmalloc(sizeof(*hash) + sizeof(struct spanhash) * (1<alloc_log2 = i; + hash->free = INITIAL_FREE(i); + memset(hash->data, 0, sizeof(struct spanhash) * (1<> 25); + accum2 = (accum2 << 7) ^ (old_1 >> 25); + accum1 += c; + if (++n < 64 && c != '\n') + continue; + hashval = (accum1 + accum2 * 0x61) % HASHBASE; + hash = add_spanhash(hash, hashval, n); + n = 0; + accum1 = accum2 = 0; + } + qsort(hash->data, + 1ul << hash->alloc_log2, + sizeof(hash->data[0]), + spanhash_cmp); + return hash; +} + +int diffcore_count_changes(struct diff_filespec *src, + struct diff_filespec *dst, + void **src_count_p, + void **dst_count_p, unsigned long delta_limit, unsigned long *src_copied, unsigned long *literal_added) { - struct linehash *src_lines, *dst_lines; + struct spanhash *s, *d; + struct spanhash_top *src_count, *dst_count; unsigned long sc, la; - src_lines = hash_lines(src, src_size); - if (!src_lines) - return -1; - dst_lines = hash_lines(dst, dst_size); - if (!dst_lines) { - free(src_lines); - return -1; + src_count = dst_count = NULL; + if (src_count_p) + src_count = *src_count_p; + if (!src_count) { + src_count = hash_chars(src); + if (src_count_p) + *src_count_p = src_count; + } + if (dst_count_p) + dst_count = *dst_count_p; + if (!dst_count) { + dst_count = hash_chars(dst); + if (dst_count_p) + *dst_count_p = dst_count; } sc = la = 0; - while (src_lines->bytes && dst_lines->bytes) { - int cmp = linehash_compare(src_lines, dst_lines); - if (!cmp) { - sc += src_lines->bytes; - src_lines++; - dst_lines++; - continue; + + s = src_count->data; + d = dst_count->data; + for (;;) { + unsigned dst_cnt, src_cnt; + if (!s->cnt) + break; /* we checked all in src */ + while (d->cnt) { + if (d->hashval >= s->hashval) + break; + d++; } - if (cmp < 0) { - src_lines++; - continue; + src_cnt = s->cnt; + dst_cnt = d->hashval == s->hashval ? d->cnt : 0; + if (src_cnt < dst_cnt) { + la += dst_cnt - src_cnt; + sc += src_cnt; } - la += dst_lines->bytes; - dst_lines++; - } - while (dst_lines->bytes) { - la += dst_lines->bytes; - dst_lines++; + else + sc += dst_cnt; + s++; } + + if (!src_count_p) + free(src_count); + if (!dst_count_p) + free(dst_count); *src_copied = sc; *literal_added = la; return 0;