#include "diff.h"
#include "diffcore.h"
-struct linehash {
- unsigned long bytes;
- unsigned long hash;
+/*
+ * Idea here is very simple.
+ *
+ * We have total of (sz-N+1) N-byte overlapping sequences in buf whose
+ * size is sz. If the same N-byte sequence appears in both source and
+ * destination, we say the byte that starts that sequence is shared
+ * between them (i.e. copied from source to destination).
+ *
+ * For each possible N-byte sequence, 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.
+ *
+ * The length of the sequence is arbitrarily set to 8 for now.
+ */
+
+/* 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 *spanhash_find(struct spanhash_top *top,
+ unsigned int hashval)
{
- /* 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;
- }
- }
- while (left) {
- unsigned c = *buf++;
- if (!c)
- goto binary;
- left--;
- if (c == '\n')
- break;
- if (' ' < c)
- hashval = hashval * 11 + c;
+ int sz = 1 << top->alloc_log2;
+ int bucket = hashval & (sz - 1);
+ while (1) {
+ struct spanhash *h = &(top->data[bucket++]);
+ if (!h->cnt)
+ return NULL;
+ if (h->hashval == hashval)
+ return h;
+ if (sz <= bucket)
+ bucket = 0;
}
- *buf_p = buf;
- return hashval;
-
- binary:
- *buf_p = NULL;
- return 0;
}
-static int linehash_compare(const void *a_, const void *b_)
+static struct spanhash_top *spanhash_rehash(struct spanhash_top *orig)
{
- 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;
+ 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;
+ }
+ }
+ free(orig);
+ return new;
}
-static struct linehash *hash_lines(const unsigned char *buf,
- unsigned long size)
+static struct spanhash_top *add_spanhash(struct spanhash_top *top,
+ unsigned int hashval, int cnt)
{
- 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;
+ int bucket, lim;
+ struct spanhash *h;
+
+ 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 struct spanhash_top *hash_chars(unsigned char *buf, unsigned int sz)
+{
+ int i, n;
+ unsigned int accum1, accum2, hashval;
+ struct spanhash_top *hash;
+
+ i = INITIAL_HASH_SIZE;
+ hash = xmalloc(sizeof(*hash) + sizeof(struct spanhash) * (1<<i));
+ hash->alloc_log2 = i;
+ hash->free = INITIAL_FREE(i);
+ memset(hash->data, 0, sizeof(struct spanhash) * (1<<i));
+
+ n = 0;
+ accum1 = accum2 = 0;
+ while (sz) {
+ unsigned int c = *buf++;
+ unsigned int old_1 = accum1;
+ sz--;
+ accum1 = (accum1 << 7) ^ (accum2 >> 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;
+ }
+ return hash;
}
int diffcore_count_changes(void *src, unsigned long src_size,
void *dst, unsigned long dst_size,
+ 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;
+ int i, ssz;
+ 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, src_size);
+ 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, dst_size);
+ 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;
- }
- if (cmp < 0) {
- src_lines++;
+
+ ssz = 1 << src_count->alloc_log2;
+ for (i = 0; i < ssz; i++) {
+ struct spanhash *s = &(src_count->data[i]);
+ struct spanhash *d;
+ unsigned dst_cnt, src_cnt;
+ if (!s->cnt)
continue;
+ src_cnt = s->cnt;
+ d = spanhash_find(dst_count, s->hashval);
+ dst_cnt = d ? 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;
}
+
+ if (!src_count_p)
+ free(src_count);
+ if (!dst_count_p)
+ free(dst_count);
*src_copied = sc;
*literal_added = la;
return 0;