Merge "Select SuffixArray size at runtime."

am: 877a6678c7

Change-Id: Ib9004b35cc50111898510baef48864cbd6790a6c
This commit is contained in:
Alex Deymo authored and android-build-merger committed 2017-10-24 17:29:27 +00:00
commit 2991153473
9 files changed
+367 -93

No files matched your search

+2
View File
@@ -60,6 +60,7 @@ cc_library_static {
"diff_encoder.cc",
"patch_writer.cc",
"split_patch_writer.cc",
"suffix_array_index.cc",
],
static_libs: [
"libdivsufsort64",
@@ -106,6 +107,7 @@ cc_test {
"extents_unittest.cc",
"patch_writer_unittest.cc",
"split_patch_writer_unittest.cc",
"suffix_array_index_unittest.cc",
"test_utils.cc",
"testrunner.cc",
],
+3 -1
View File
@@ -37,7 +37,8 @@ bsdiff_src_files := \
bz2_compressor.cc \
diff_encoder.cc \
patch_writer.cc \
split_patch_writer.cc
split_patch_writer.cc \
suffix_array_index.cc
# "bspatch" program.
bspatch_src_files := \
@@ -58,6 +59,7 @@ bsdiff_common_unittests := \
extents_unittest.cc \
patch_writer_unittest.cc \
split_patch_writer_unittest.cc \
suffix_array_index_unittest.cc \
test_utils.cc \
testrunner.cc
+37 -73
View File
@@ -43,61 +43,21 @@ __FBSDID("$FreeBSD: src/usr.bin/bsdiff/bsdiff/bsdiff.c,v 1.1 2005/08/06 01:59:05
#include "bsdiff/diff_encoder.h"
#include "bsdiff/patch_writer.h"
#include "bsdiff/suffix_array_index.h"
namespace bsdiff {
static off_t matchlen(const u_char* old, off_t oldsize, const u_char* new_buf,
off_t newsize) {
off_t i;
for(i=0;(i<oldsize)&&(i<newsize);i++)
if(old[i]!=new_buf[i]) break;
return i;
}
// This is a binary search of the string |new_buf| of size |newsize| (or a
// prefix of it) in the |old| string with size |oldsize| using the suffix array
// |I|. |st| and |en| is the start and end of the search range (inclusive).
// Returns the length of the longest prefix found and stores the position of the
// string found in |*pos|.
static off_t search(saidx_t* I, const u_char* old, off_t oldsize,
const u_char* new_buf, off_t newsize, off_t st, off_t en,
off_t* pos) {
off_t x,y;
if(en-st<2) {
x=matchlen(old+I[st],oldsize-I[st],new_buf,newsize);
y=matchlen(old+I[en],oldsize-I[en],new_buf,newsize);
if(x>y) {
*pos=I[st];
return x;
} else {
*pos=I[en];
return y;
}
};
x=st+(en-st)/2;
if(memcmp(old+I[x],new_buf,std::min(oldsize-I[x],newsize))<=0) {
return search(I,old,oldsize,new_buf,newsize,x,en,pos);
} else {
return search(I,old,oldsize,new_buf,newsize,st,x,pos);
};
}
int bsdiff(const char* old_filename, const char* new_filename,
const char* patch_filename) {
int fd;
u_char *old_buf,*new_buf;
off_t oldsize,newsize;
uint8_t *old_buf,*new_buf;
ssize_t oldsize,newsize;
/* Allocate oldsize+1 bytes instead of oldsize bytes to ensure
that we never try to malloc(0) and get a NULL pointer */
if(((fd=open(old_filename,O_RDONLY,0))<0) ||
((oldsize=lseek(fd,0,SEEK_END))==-1) ||
((old_buf=static_cast<u_char*>(malloc(oldsize+1)))==NULL) ||
((old_buf=static_cast<uint8_t*>(malloc(oldsize+1)))==NULL) ||
(lseek(fd,0,SEEK_SET)!=0) ||
(read(fd,old_buf,oldsize)!=oldsize) ||
(close(fd)==-1)) err(1,"%s",old_filename);
@@ -106,7 +66,7 @@ int bsdiff(const char* old_filename, const char* new_filename,
that we never try to malloc(0) and get a NULL pointer */
if(((fd=open(new_filename,O_RDONLY,0))<0) ||
((newsize=lseek(fd,0,SEEK_END))==-1) ||
((new_buf = static_cast<u_char*>(malloc(newsize+1)))==NULL) ||
((new_buf = static_cast<uint8_t*>(malloc(newsize+1)))==NULL) ||
(lseek(fd,0,SEEK_SET)!=0) ||
(read(fd,new_buf,newsize)!=newsize) ||
(close(fd)==-1)) err(1,"%s",new_filename);
@@ -122,32 +82,39 @@ int bsdiff(const char* old_filename, const char* new_filename,
// TODO(deymo): Deprecate this version of the interface and move all callers
// to the underlying version using PatchWriterInterface instead. This allows
// more flexible options including different encodings.
int bsdiff(const u_char* old_buf, off_t oldsize, const u_char* new_buf,
off_t newsize, const char* patch_filename, saidx_t** I_cache) {
int bsdiff(const uint8_t* old_buf, size_t oldsize, const uint8_t* new_buf,
size_t newsize, const char* patch_filename,
SuffixArrayIndexInterface** sai_cache) {
BsdiffPatchWriter patch(patch_filename);
return bsdiff(old_buf, oldsize, new_buf, newsize, &patch, I_cache);
return bsdiff(old_buf, oldsize, new_buf, newsize, &patch, sai_cache);
}
int bsdiff(const u_char* old_buf, off_t oldsize, const u_char* new_buf,
off_t newsize, PatchWriterInterface* patch, saidx_t** I_cache) {
saidx_t *I;
off_t scan,pos=0,len;
off_t lastscan,lastpos,lastoffset;
off_t oldscore,scsc;
off_t s,Sf,lenf,Sb,lenb;
off_t overlap,Ss,lens;
off_t i;
int bsdiff(const uint8_t* old_buf, size_t oldsize, const uint8_t* new_buf,
size_t newsize, PatchWriterInterface* patch,
SuffixArrayIndexInterface** sai_cache) {
size_t scsc, scan;
uint64_t pos=0;
size_t len;
size_t lastscan,lastpos,lastoffset;
uint64_t oldscore;
ssize_t s,Sf,lenf,Sb,lenb;
ssize_t overlap,Ss,lens;
ssize_t i;
if (I_cache && *I_cache) {
I = *I_cache;
std::unique_ptr<SuffixArrayIndexInterface> local_sai;
SuffixArrayIndexInterface* sai;
if (sai_cache && *sai_cache) {
sai = *sai_cache;
} else {
if ((I=static_cast<saidx_t*>(malloc((oldsize+1)*sizeof(saidx_t))))==NULL)
err(1,NULL);
local_sai = CreateSuffixArrayIndex(old_buf, oldsize);
if (!local_sai)
return 1;
sai = local_sai.get();
// Note: divsufsort() fails when the passed size is 0 and old_buf is NULL.
if (oldsize > 0 && divsufsort(old_buf, I, oldsize)) err(1, "divsufsort");
if (I_cache)
*I_cache = I;
// Transfer ownership to the caller.
if (sai_cache)
*sai_cache = local_sai.release();
}
/* Initialize the patch file encoder */
@@ -166,14 +133,14 @@ int bsdiff(const u_char* old_buf, off_t oldsize, const u_char* new_buf,
* go past that block of data. We need to track the number of
* times we're stuck in the block and break out of it. */
int num_less_than_eight = 0;
off_t prev_len, prev_oldscore, prev_pos;
size_t prev_len;
uint64_t prev_pos, prev_oldscore;
for(scsc=scan+=len;scan<newsize;scan++) {
prev_len=len;
prev_oldscore=oldscore;
prev_pos=pos;
len=search(I,old_buf,oldsize,new_buf+scan,newsize-scan,
0,oldsize-1,&pos);
sai->SearchPrefix(new_buf + scan, newsize - scan, &len, &pos);
for(;scsc<scan+len;scsc++)
if((scsc+lastoffset<oldsize) &&
@@ -187,7 +154,7 @@ int bsdiff(const u_char* old_buf, off_t oldsize, const u_char* new_buf,
(old_buf[scan+lastoffset] == new_buf[scan]))
oldscore--;
const off_t fuzz = 8;
const size_t fuzz = 8;
if (prev_len-fuzz<=len && len<=prev_len &&
prev_oldscore-fuzz<=oldscore &&
oldscore<=prev_oldscore &&
@@ -210,7 +177,7 @@ int bsdiff(const u_char* old_buf, off_t oldsize, const u_char* new_buf,
lenb=0;
if(scan<newsize) {
s=0;Sb=0;
for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
for(i=1;(scan>=lastscan+i)&&(pos>=static_cast<uint64_t>(i));i++) {
if(old_buf[pos-i]==new_buf[scan-i]) s++;
if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
};
@@ -245,9 +212,6 @@ int bsdiff(const u_char* old_buf, off_t oldsize, const u_char* new_buf,
if (!diff_encoder.Close())
errx(1, "Closing the patch file");
if (I_cache == nullptr)
free(I);
return 0;
}
+2
View File
@@ -57,6 +57,7 @@
'diff_encoder.cc',
'patch_writer.cc',
'split_patch_writer.cc',
'suffix_array_index.cc',
],
},
# bsdiff executable
@@ -133,6 +134,7 @@
'extents_unittest.cc',
'patch_writer_unittest.cc',
'split_patch_writer_unittest.cc',
'suffix_array_index_unittest.cc',
'test_utils.cc',
],
},
+12 -19
View File
@@ -5,18 +5,11 @@
#ifndef _BSDIFF_BSDIFF_H_
#define _BSDIFF_BSDIFF_H_
#include <sys/types.h>
#if _FILE_OFFSET_BITS == 64
#include "divsufsort64.h"
#define saidx_t saidx64_t
#define divsufsort divsufsort64
#else
#include "divsufsort.h"
#endif
#include <stdint.h>
#include "bsdiff/common.h"
#include "bsdiff/patch_writer_interface.h"
#include "bsdiff/suffix_array_index_interface.h"
namespace bsdiff {
@@ -32,20 +25,20 @@ int bsdiff(const char* old_filename,
// |I_cache| can be used to cache the suffix array if the same |old_buf| is used
// repeatedly, pass nullptr if not needed.
BSDIFF_EXPORT
int bsdiff(const u_char* old_buf,
off_t oldsize,
const u_char* new_buf,
off_t newsize,
int bsdiff(const uint8_t* old_buf,
size_t oldsize,
const uint8_t* new_buf,
size_t newsize,
const char* patch_filename,
saidx_t** I_cache);
SuffixArrayIndexInterface** sai_cache);
BSDIFF_EXPORT
int bsdiff(const u_char* old_buf,
off_t oldsize,
const u_char* new_buf,
off_t newsize,
int bsdiff(const uint8_t* old_buf,
size_t oldsize,
const uint8_t* new_buf,
size_t newsize,
PatchWriterInterface* patch,
saidx_t** I_cache);
SuffixArrayIndexInterface** sai_cache);
} // namespace bsdiff
@@ -0,0 +1,36 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef _BSDIFF_SUFFIX_ARRAY_INDEX_INTERFACE_H_
#define _BSDIFF_SUFFIX_ARRAY_INDEX_INTERFACE_H_
// The SuffixArrayIndexInterface encapsulates a search index based on a
// suffix-array with a common string search interface. The implementations of
// this index can vary on technical details, such as the size of the internal
// suffix array elements, which are not visible in this interface.
#include <stdint.h>
namespace bsdiff {
class SuffixArrayIndexInterface {
public:
virtual ~SuffixArrayIndexInterface() = default;
// Search in the index the longest prefix of the string |target| of length
// |length|. The length of the longest prefix found, which could be 0, is
// stored in |out_length| and a position in the source text where this prefix
// was found is store in |out_pos|.
virtual void SearchPrefix(const uint8_t* target,
size_t length,
size_t* out_length,
uint64_t* out_pos) const = 0;
protected:
SuffixArrayIndexInterface() = default;
};
} // namespace bsdiff
#endif // _BSDIFF_SUFFIX_ARRAY_INDEX_INTERFACE_H_
+174
View File
@@ -0,0 +1,174 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "bsdiff/suffix_array_index.h"
#include <limits>
#include <vector>
#include <divsufsort.h>
#include <divsufsort64.h>
#include "bsdiff/logging.h"
using std::endl;
namespace {
// libdivsufsort C++ overloaded functions used to allow calling the right
// implementation based on the pointer size.
int CallDivSufSort(const uint8_t* text, saidx_t* sa, size_t n) {
return divsufsort(text, sa, n);
}
int CallDivSufSort(const uint8_t* text, saidx64_t* sa, size_t n) {
return divsufsort64(text, sa, n);
}
saidx_t CallSaSearch(const uint8_t* text,
size_t text_size,
const uint8_t* pattern,
size_t pattern_size,
const saidx_t* sa,
size_t sa_size,
saidx_t* left) {
return sa_search(text, text_size, pattern, pattern_size, sa, sa_size, left);
}
saidx64_t CallSaSearch(const uint8_t* text,
size_t text_size,
const uint8_t* pattern,
size_t pattern_size,
const saidx64_t* sa,
size_t sa_size,
saidx64_t* left) {
return sa_search64(text, text_size, pattern, pattern_size, sa, sa_size, left);
}
} // namespace
namespace bsdiff {
// The SAIDX template type must be either saidx_t or saidx64_t, which will
// depend on the maximum size of the input text needed.
template <typename SAIDX>
class SuffixArrayIndex : public SuffixArrayIndexInterface {
public:
SuffixArrayIndex() = default;
// Initialize and construct the suffix array of the |text| string of length
// |n|. The memory pointed by |text| must be kept alive. Returns whether the
// construction succeeded.
bool Init(const uint8_t* text, size_t n);
// SuffixArrayIndexInterface overrides.
void SearchPrefix(const uint8_t* target,
size_t length,
size_t* out_length,
uint64_t* out_pos) const override;
private:
const uint8_t* text_{nullptr}; // Owned by the caller.
size_t n_{0};
std::vector<SAIDX> sa_;
};
template <typename SAIDX>
bool SuffixArrayIndex<SAIDX>::Init(const uint8_t* text, size_t n) {
if (!sa_.empty()) {
// Already initialized.
LOG(ERROR) << "SuffixArray already initialized" << endl;
return false;
}
if (static_cast<uint64_t>(n) >
static_cast<uint64_t>(std::numeric_limits<SAIDX>::max())) {
LOG(ERROR) << "Input too big (" << n << ") for this implementation" << endl;
return false;
}
text_ = text;
n_ = n;
sa_.resize(n + 1);
if (n > 0 && CallDivSufSort(text_, sa_.data(), n) != 0) {
LOG(ERROR) << "divsufsrot() failed" << endl;
return false;
}
return true;
}
template <typename SAIDX>
void SuffixArrayIndex<SAIDX>::SearchPrefix(const uint8_t* target,
size_t length,
size_t* out_length,
uint64_t* out_pos) const {
SAIDX suf_left;
SAIDX count =
CallSaSearch(text_, n_, target, length, sa_.data(), n_, &suf_left);
if (count > 0) {
// This is the simple case where we found the whole |target| string was
// found.
*out_pos = sa_[suf_left];
*out_length = length;
return;
}
// In this case, |suf_left| points to the first suffix array position such
// that the suffix at that position is lexicographically larger than |target|.
// We only need to check whether the previous entry or the current entry is a
// longer match.
size_t prev_suffix_len = 0;
if (suf_left > 0) {
const size_t prev_max_len =
std::min(n_ - static_cast<size_t>(sa_[suf_left - 1]), length);
const uint8_t* prev_suffix = text_ + sa_[suf_left - 1];
prev_suffix_len =
std::mismatch(target, target + prev_max_len, prev_suffix).first -
target;
}
size_t next_suffix_len = 0;
if (static_cast<size_t>(suf_left) < n_) {
const uint8_t* next_suffix = text_ + sa_[suf_left];
const size_t next_max_len =
std::min(n_ - static_cast<size_t>(sa_[suf_left]), length);
next_suffix_len =
std::mismatch(target, target + next_max_len, next_suffix).first -
target;
}
*out_length = std::max(next_suffix_len, prev_suffix_len);
if (!*out_length) {
*out_pos = 0;
} else if (next_suffix_len >= prev_suffix_len) {
*out_pos = sa_[suf_left];
} else {
*out_pos = sa_[suf_left - 1];
}
}
std::unique_ptr<SuffixArrayIndexInterface> CreateSuffixArrayIndex(
const uint8_t* text,
size_t n) {
// The maximum supported size when using the suffix array based on the 32-bit
// saidx_t type. We limit this to something a bit smaller (16 bytes smaller)
// than the maximum representable number so references like "n + 1" are don't
// overflow.
const size_t kMaxSaidxSize = std::numeric_limits<saidx_t>::max() - 16;
std::unique_ptr<SuffixArrayIndexInterface> ret;
if (n > kMaxSaidxSize) {
SuffixArrayIndex<saidx64_t>* sa_ptr = new SuffixArrayIndex<saidx64_t>();
ret.reset(sa_ptr);
if (!sa_ptr->Init(text, n))
return nullptr;
} else {
SuffixArrayIndex<saidx_t>* sa_ptr = new SuffixArrayIndex<saidx_t>();
ret.reset(sa_ptr);
if (!sa_ptr->Init(text, n))
return nullptr;
}
return ret;
}
} // namespace bsdiff
+22
View File
@@ -0,0 +1,22 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef _BSDIFF_SUFFIX_ARRAY_INDEX_H_
#define _BSDIFF_SUFFIX_ARRAY_INDEX_H_
#include <stdint.h>
#include <memory>
#include "bsdiff/suffix_array_index_interface.h"
namespace bsdiff {
std::unique_ptr<SuffixArrayIndexInterface> CreateSuffixArrayIndex(
const uint8_t* text,
size_t n);
} // namespace bsdiff
#endif // _BSDIFF_SUFFIX_ARRAY_INDEX_H_
+79
View File
@@ -0,0 +1,79 @@
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "bsdiff/suffix_array_index.h"
#include <stdlib.h>
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace bsdiff {
class SuffixArrayIndexTest : public ::testing::Test {
protected:
void InitSA(const std::string& text) {
text_.resize(text.size());
std::copy(text.begin(), text.end(), text_.data());
sai_ = CreateSuffixArrayIndex(text_.data(), text_.size());
}
void SearchPrefix(const std::string& pattern,
size_t* out_length,
uint64_t* out_pos) {
sai_->SearchPrefix(reinterpret_cast<const uint8_t*>(pattern.data()),
pattern.size(), out_length, out_pos);
// Check that the returned values are indeed a valid prefix.
EXPECT_LE(*out_length, pattern.size());
ASSERT_LT(*out_pos, text_.size());
ASSERT_LE(*out_pos + *out_length, text_.size());
EXPECT_EQ(0, memcmp(text_.data() + *out_pos, pattern.data(), *out_length));
}
std::vector<uint8_t> text_;
std::unique_ptr<SuffixArrayIndexInterface> sai_;
};
// Test that the suffix array index can be initialized with an example text.
TEST_F(SuffixArrayIndexTest, MississippiTest) {
InitSA("mississippi");
}
TEST_F(SuffixArrayIndexTest, EmptySuffixArrayTest) {
InitSA("");
}
// Test various corner cases while searching for prefixes.
TEST_F(SuffixArrayIndexTest, SearchPrefixTest) {
InitSA("abc1_abc2_abc3_ab_abcd");
uint64_t pos;
size_t length;
// The string is not found at all.
SearchPrefix("zzz", &length, &pos); // lexicographically highest.
EXPECT_EQ(0U, length);
SearchPrefix(" ", &length, &pos); // lexicographically lowest.
EXPECT_EQ(0U, length);
// The exact pattern is found multiple times.
SearchPrefix("abc", &length, &pos);
EXPECT_EQ(3U, length);
// The exact pattern is found only one time, at the end of the string.
SearchPrefix("abcd", &length, &pos);
EXPECT_EQ(4U, length);
EXPECT_EQ(18U, pos);
// The string is not found, but a prefix is found.
SearchPrefix("abcW", &length, &pos);
EXPECT_EQ(3U, length);
}
} // namespace bsdiff