restic/internal/restorer/sparsewrite.go
greatroar 5d4568d393 Write sparse files in restorer
This writes files by using (*os.File).Truncate, which resolves to the
truncate system call on Unix.

Compared to the naive loop,

	for _, b := range p {
		if b != 0 {
			return false
		}
	}

the optimized allZero is about 10× faster:

name       old time/op    new time/op     delta
AllZero-8    1.09ms ± 1%     0.09ms ± 1%    -92.10%  (p=0.000 n=10+10)

name       old speed      new speed       delta
AllZero-8  3.84GB/s ± 1%  48.59GB/s ± 1%  +1166.51%  (p=0.000 n=10+10)
2022-09-24 21:18:48 +02:00

60 lines
1.3 KiB
Go

//go:build !windows
// +build !windows
package restorer
import "bytes"
// WriteAt writes p to f.File at offset. It tries to do a sparse write
// and updates f.size.
func (f *partialFile) WriteAt(p []byte, offset int64) (n int, err error) {
n = len(p)
end := offset + int64(n)
// Skip the longest all-zero prefix of p.
// If it's long enough, we can punch a hole in the file.
skipped := zeroPrefixLen(p)
p = p[skipped:]
offset += int64(skipped)
switch {
case len(p) == 0 && end > f.size:
// We need to do a Truncate, as WriteAt with length-0 input
// doesn't actually extend the file.
err = f.Truncate(end)
if err != nil {
return 0, err
}
case len(p) == 0:
// All zeros, file already big enough. A previous WriteAt or
// Truncate will have produced the zeros in f.File.
default:
n, err = f.File.WriteAt(p, offset)
}
end = offset + int64(n)
if end > f.size {
f.size = end
}
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
}