diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 625ad9b16..f41ce38a5 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -813,7 +813,14 @@ func (r *Repository) SaveBlob(ctx context.Context, t restic.BlobType, buf []byte // compute plaintext hash if not already set if id.IsNull() { - newID = restic.Hash(buf) + // Special case the hash calculation for all zero chunks. This is especially + // useful for sparse files containing large all zero regions. For these we can + // process chunks as fast as we can read the from disk. + if len(buf) == chunker.MinSize && restic.ZeroPrefixLen(buf) == chunker.MinSize { + newID = ZeroChunk() + } else { + newID = restic.Hash(buf) + } } else { newID = id } @@ -967,3 +974,14 @@ func streamPackPart(ctx context.Context, beLoad BackendLoadFn, key *crypto.Key, }) return errors.Wrap(err, "StreamPack") } + +var zeroChunkOnce sync.Once +var zeroChunkID restic.ID + +// ZeroChunk computes and returns (cached) the ID of an all-zero chunk with size chunker.MinSize +func ZeroChunk() restic.ID { + zeroChunkOnce.Do(func() { + zeroChunkID = restic.Hash(make([]byte, chunker.MinSize)) + }) + return zeroChunkID +} diff --git a/internal/restorer/filerestorer.go b/internal/restorer/filerestorer.go index 659458cd8..2deef1cd2 100644 --- a/internal/restorer/filerestorer.go +++ b/internal/restorer/filerestorer.go @@ -7,7 +7,6 @@ import ( "golang.org/x/sync/errgroup" - "github.com/restic/chunker" "github.com/restic/restic/internal/crypto" "github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/errors" @@ -76,7 +75,7 @@ func newFileRestorer(dst string, idx: idx, packLoader: packLoader, filesWriter: newFilesWriter(workerCount), - zeroChunk: restic.Hash(make([]byte, chunker.MinSize)), + zeroChunk: repository.ZeroChunk(), sparse: sparse, workerCount: workerCount, dst: dst,