diff --git a/internal/restic/zeroprefix.go b/internal/restic/zeroprefix.go new file mode 100644 index 000000000..b25e7ab27 --- /dev/null +++ b/internal/restic/zeroprefix.go @@ -0,0 +1,21 @@ +package restic + +import "bytes" + +// ZeroPrefixLen returns the length of the longest all-zero prefix of p. +func ZeroPrefixLen(p []byte) (n int) { + // First skip 1kB-sized blocks, for speed. + var zeros [1024]byte + + for len(p) >= len(zeros) && bytes.Equal(p[:len(zeros)], zeros[:]) { + p = p[len(zeros):] + n += len(zeros) + } + + for len(p) > 0 && p[0] == 0 { + p = p[1:] + n++ + } + + return n +} diff --git a/internal/restic/zeroprefix_test.go b/internal/restic/zeroprefix_test.go new file mode 100644 index 000000000..a21806851 --- /dev/null +++ b/internal/restic/zeroprefix_test.go @@ -0,0 +1,52 @@ +package restic_test + +import ( + "math/rand" + "testing" + + "github.com/restic/restic/internal/restic" + "github.com/restic/restic/internal/test" +) + +func TestZeroPrefixLen(t *testing.T) { + var buf [2048]byte + + // test zero prefixes of various lengths + for i := len(buf) - 1; i >= 0; i-- { + buf[i] = 42 + skipped := restic.ZeroPrefixLen(buf[:]) + test.Equals(t, i, skipped) + } + // test buffers of various sizes + for i := 0; i < len(buf); i++ { + skipped := restic.ZeroPrefixLen(buf[i:]) + test.Equals(t, 0, skipped) + } +} + +func BenchmarkZeroPrefixLen(b *testing.B) { + var ( + buf [4<<20 + 37]byte + r = rand.New(rand.NewSource(0x618732)) + sumSkipped int64 + ) + + b.ReportAllocs() + b.SetBytes(int64(len(buf))) + b.ResetTimer() + + for i := 0; i < b.N; i++ { + j := r.Intn(len(buf)) + buf[j] = 0xff + + skipped := restic.ZeroPrefixLen(buf[:]) + sumSkipped += int64(skipped) + + buf[j] = 0 + } + + // The closer this is to .5, the better. If it's far off, give the + // benchmark more time to run with -benchtime. + b.Logf("average number of zeros skipped: %.3f", + float64(sumSkipped)/(float64(b.N*len(buf)))) +} diff --git a/internal/restorer/restorer_unix_test.go b/internal/restorer/restorer_unix_test.go index 9e5f61ff4..dc8d6adeb 100644 --- a/internal/restorer/restorer_unix_test.go +++ b/internal/restorer/restorer_unix_test.go @@ -8,7 +8,6 @@ import ( "context" "io/ioutil" "math" - "math/rand" "os" "path/filepath" "syscall" @@ -123,30 +122,3 @@ func TestRestorerSparseFiles(t *testing.T) { t.Logf("wrote %d zeros as %d blocks, %.1f%% sparse", len(zeros), st.Blocks, 100*sparsity) } - -func BenchmarkZeroPrefixLen(b *testing.B) { - var ( - buf [4<<20 + 37]byte - r = rand.New(rand.NewSource(0x618732)) - sumSkipped int64 - ) - - b.ReportAllocs() - b.SetBytes(int64(len(buf))) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - j := r.Intn(len(buf)) - buf[j] = 0xff - - skipped := zeroPrefixLen(buf[:]) - sumSkipped += int64(skipped) - - buf[j] = 0 - } - - // The closer this is to .5, the better. If it's far off, give the - // benchmark more time to run with -benchtime. - b.Logf("average number of zeros skipped: %.3f", - float64(sumSkipped)/(float64(b.N*len(buf)))) -} diff --git a/internal/restorer/sparsewrite.go b/internal/restorer/sparsewrite.go index 9dec4bfa3..2c1f234de 100644 --- a/internal/restorer/sparsewrite.go +++ b/internal/restorer/sparsewrite.go @@ -3,7 +3,9 @@ package restorer -import "bytes" +import ( + "github.com/restic/restic/internal/restic" +) // WriteAt writes p to f.File at offset. It tries to do a sparse write // and updates f.size. @@ -16,7 +18,7 @@ func (f *partialFile) WriteAt(p []byte, offset int64) (n int, err error) { // Skip the longest all-zero prefix of p. // If it's long enough, we can punch a hole in the file. - skipped := zeroPrefixLen(p) + skipped := restic.ZeroPrefixLen(p) p = p[skipped:] offset += int64(skipped) @@ -33,21 +35,3 @@ func (f *partialFile) WriteAt(p []byte, offset int64) (n int, err error) { return n, err } - -// zeroPrefixLen returns the length of the longest all-zero prefix of p. -func zeroPrefixLen(p []byte) (n int) { - // First skip 1kB-sized blocks, for speed. - var zeros [1024]byte - - for len(p) >= len(zeros) && bytes.Equal(p[:len(zeros)], zeros[:]) { - p = p[len(zeros):] - n += len(zeros) - } - - for len(p) > 0 && p[0] == 0 { - p = p[1:] - n++ - } - - return n -}