summaryrefslogtreecommitdiff
path: root/subversion/libsvn_delta/xdelta.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_delta/xdelta.c')
-rw-r--r--subversion/libsvn_delta/xdelta.c142
1 files changed, 62 insertions, 80 deletions
diff --git a/subversion/libsvn_delta/xdelta.c b/subversion/libsvn_delta/xdelta.c
index 2075a51..2e5bb26 100644
--- a/subversion/libsvn_delta/xdelta.c
+++ b/subversion/libsvn_delta/xdelta.c
@@ -29,6 +29,7 @@
#include "svn_hash.h"
#include "svn_delta.h"
+#include "private/svn_string_private.h"
#include "delta.h"
/* This is pseudo-adler32. It is adler32 without the prime modulus.
@@ -43,6 +44,15 @@
*/
#define MATCH_BLOCKSIZE 64
+/* Size of the checksum presence FLAGS array in BLOCKS_T. With standard
+ MATCH_BLOCKSIZE and SVN_DELTA_WINDOW_SIZE, 32k entries is about 20x
+ the number of checksums that actually occur, i.e. we expect a >95%
+ probability that non-matching checksums get already detected by checking
+ against the FLAGS array.
+ Must be a power of 2.
+ */
+#define FLAGS_COUNT (32 * 1024)
+
/* "no" / "invalid" / "unused" value for positions within the delta windows
*/
#define NO_POSITION ((apr_uint32_t)-1)
@@ -104,7 +114,7 @@ struct block
(our delta window size much much smaller then 4GB).
That reduces the hash table size by 50% from 32to 16KB
and makes it easier to fit into the CPU's L1 cache. */
- apr_uint32_t pos; /* NO_POSITION -> block is not used */
+ apr_uint32_t pos; /* NO_POSITION -> block is not used */
};
/* A hash table, using open addressing, of the blocks of the source. */
@@ -117,8 +127,19 @@ struct blocks
hte same width as the block position index, (struct
block).pos. */
apr_uint32_t max;
+
/* Source buffer that the positions in SLOTS refer to. */
const char* data;
+
+ /* Bit array indicating whether there may be a matching slot for a given
+ adler32 checksum. Since FLAGS has much more entries than SLOTS, this
+ will indicate most cases of non-matching checksums with a "0" bit, i.e.
+ as "known not to have a match".
+ The mapping of adler32 checksum bits is [0..2][16..27] (LSB -> MSB),
+ i.e. address the byte by the multiplicative part of adler32 and address
+ the bits in that byte by the additive part of adler32. */
+ char flags[FLAGS_COUNT / 8];
+
/* The vector of blocks. A pos value of NO_POSITION represents an unused
slot. */
struct block *slots;
@@ -135,6 +156,15 @@ static apr_uint32_t hash_func(apr_uint32_t sum)
return sum ^ (sum >> 12);
}
+/* Return the offset in BLOCKS.FLAGS for the adler32 SUM. */
+static apr_uint32_t hash_flags(apr_uint32_t sum)
+{
+ /* The upper half of SUM has a wider value range than the lower 16 bit.
+ Also, we want to a different folding than HASH_FUNC to minimize
+ correlation between different hash levels. */
+ return (sum >> 16) & ((FLAGS_COUNT / 8) - 1);
+}
+
/* Insert a block with the checksum ADLERSUM at position POS in the source
data into the table BLOCKS. Ignore true duplicates, i.e. blocks with
actually the same content. */
@@ -152,6 +182,7 @@ add_block(struct blocks *blocks, apr_uint32_t adlersum, apr_uint32_t pos)
blocks->slots[h].adlersum = adlersum;
blocks->slots[h].pos = pos;
+ blocks->flags[hash_flags(adlersum)] |= 1 << (adlersum & 7);
}
/* Find a block in BLOCKS with the checksum ADLERSUM and matching the content
@@ -216,6 +247,9 @@ init_blocks_table(const char *data,
blocks->slots[i].pos = NO_POSITION;
}
+ /* No checksum entries in SLOTS, yet => reset all checksum flags. */
+ memset(blocks->flags, 0, sizeof(blocks->flags));
+
/* If there is an odd block at the end of the buffer, we will
not use that shorter block for deltification (only indirectly
as an extension of some previous block). */
@@ -223,73 +257,6 @@ init_blocks_table(const char *data,
add_block(blocks, init_adler32(data + i), i);
}
-/* Return the lowest position at which A and B differ. If no difference
- * can be found in the first MAX_LEN characters, MAX_LEN will be returned.
- */
-static apr_size_t
-match_length(const char *a, const char *b, apr_size_t max_len)
-{
- apr_size_t pos = 0;
-
-#if SVN_UNALIGNED_ACCESS_IS_OK
-
- /* Chunky processing is so much faster ...
- *
- * We can't make this work on architectures that require aligned access
- * because A and B will probably have different alignment. So, skipping
- * the first few chars until alignment is reached is not an option.
- */
- for (; pos + sizeof(apr_size_t) <= max_len; pos += sizeof(apr_size_t))
- if (*(const apr_size_t*)(a + pos) != *(const apr_size_t*)(b + pos))
- break;
-
-#endif
-
- for (; pos < max_len; ++pos)
- if (a[pos] != b[pos])
- break;
-
- return pos;
-}
-
-/* Return the number of bytes before A and B that don't differ. If no
- * difference can be found in the first MAX_LEN characters, MAX_LEN will
- * be returned. Please note that A-MAX_LEN and B-MAX_LEN must both be
- * valid addresses.
- */
-static apr_size_t
-reverse_match_length(const char *a, const char *b, apr_size_t max_len)
-{
- apr_size_t pos = 0;
-
-#if SVN_UNALIGNED_ACCESS_IS_OK
-
- /* Chunky processing is so much faster ...
- *
- * We can't make this work on architectures that require aligned access
- * because A and B will probably have different alignment. So, skipping
- * the first few chars until alignment is reached is not an option.
- */
- for (pos = sizeof(apr_size_t); pos <= max_len; pos += sizeof(apr_size_t))
- if (*(const apr_size_t*)(a - pos) != *(const apr_size_t*)(b - pos))
- break;
-
- pos -= sizeof(apr_size_t);
-
-#endif
-
- /* If we find a mismatch at -pos, pos-1 characters matched.
- */
- while (++pos <= max_len)
- if (a[0-pos] != b[0-pos])
- return pos - 1;
-
- /* No mismatch found -> at least MAX_LEN matching chars.
- */
- return max_len;
-}
-
-
/* Try to find a match for the target data B in BLOCKS, and then
extend the match as long as data in A and B at the match position
continues to match. We set the position in A we ended up in (in
@@ -323,9 +290,9 @@ find_match(const struct blocks *blocks,
max_delta = asize - apos - MATCH_BLOCKSIZE < bsize - bpos - MATCH_BLOCKSIZE
? asize - apos - MATCH_BLOCKSIZE
: bsize - bpos - MATCH_BLOCKSIZE;
- delta = match_length(a + apos + MATCH_BLOCKSIZE,
- b + bpos + MATCH_BLOCKSIZE,
- max_delta);
+ delta = svn_cstring__match_length(a + apos + MATCH_BLOCKSIZE,
+ b + bpos + MATCH_BLOCKSIZE,
+ max_delta);
/* See if we can extend backwards (max MATCH_BLOCKSIZE-1 steps because A's
content has been sampled only every MATCH_BLOCKSIZE positions). */
@@ -362,7 +329,8 @@ store_delta_trailer(svn_txdelta__ops_baton_t *build_baton,
if (max_len == 0)
return;
- end_match = reverse_match_length(a + asize, b + bsize, max_len);
+ end_match = svn_cstring__reverse_match_length(a + asize, b + bsize,
+ max_len);
if (end_match <= 4)
end_match = 0;
@@ -409,12 +377,12 @@ compute_delta(svn_txdelta__ops_baton_t *build_baton,
{
struct blocks blocks;
apr_uint32_t rolling;
- apr_size_t lo = 0, pending_insert_start = 0;
+ apr_size_t lo = 0, pending_insert_start = 0, upper;
/* Optimization: directly compare window starts. If more than 4
* bytes match, we can immediately create a matching windows.
* Shorter sequences result in a net data increase. */
- lo = match_length(a, b, asize > bsize ? bsize : asize);
+ lo = svn_cstring__match_length(a, b, asize > bsize ? bsize : asize);
if ((lo > 4) || (lo == bsize))
{
svn_txdelta__insert_op(build_baton, svn_txdelta_source,
@@ -432,19 +400,32 @@ compute_delta(svn_txdelta__ops_baton_t *build_baton,
return;
}
+ upper = bsize - MATCH_BLOCKSIZE; /* this is now known to be >= LO */
+
/* Initialize the matches table. */
init_blocks_table(a, asize, &blocks, pool);
/* Initialize our rolling checksum. */
rolling = init_adler32(b + lo);
- while (lo < bsize)
+ while (lo < upper)
{
- apr_size_t matchlen = 0;
+ apr_size_t matchlen;
apr_size_t apos;
- if (lo + MATCH_BLOCKSIZE <= bsize)
- matchlen = find_match(&blocks, rolling, a, asize, b, bsize,
- &lo, &apos, pending_insert_start);
+ /* Quickly skip positions whose respective ROLLING checksums
+ definitely do not match any SLOT in BLOCKS. */
+ while (!(blocks.flags[hash_flags(rolling)] & (1 << (rolling & 7)))
+ && lo < upper)
+ {
+ rolling = adler32_replace(rolling, b[lo], b[lo+MATCH_BLOCKSIZE]);
+ lo++;
+ }
+
+ /* LO is still <= UPPER, i.e. the following lookup is legal:
+ Closely check whether we've got a match for the current location.
+ Due to the above pre-filter, chances are that we find one. */
+ matchlen = find_match(&blocks, rolling, a, asize, b, bsize,
+ &lo, &apos, pending_insert_start);
/* If we didn't find a real match, insert the byte at the target
position into the pending insert. */
@@ -468,7 +449,8 @@ compute_delta(svn_txdelta__ops_baton_t *build_baton,
{
/* the match borders on the previous op. Maybe, we found a
* match that is better than / overlapping the previous one. */
- apr_size_t len = reverse_match_length(a + apos, b + lo, apos < lo ? apos : lo);
+ apr_size_t len = svn_cstring__reverse_match_length
+ (a + apos, b + lo, apos < lo ? apos : lo);
if (len > 0)
{
len = svn_txdelta__remove_copy(build_baton, len);