Files
kopia/internal/gather/gather_write_buffer.go
Jarek Kowalski 7eca44993f feat(providers): improve performance of SFTP uploads (#3235)
This improves the performance of gather writes (p and q blobs) in
particular on high-latency links by reducing round-trips.

Tested by uploading Kopia working directory
(3 GB, files:32157 dirs:6051) to SFTP server on LAN (TrueNAS) over WiFi:

Before: 2m4s (~24 MB/s)
After: 1m13s (~41 MB/s)
2023-08-19 18:07:43 -07:00

182 lines
3.8 KiB
Go

package gather
import (
"io"
"sync"
"github.com/kopia/kopia/repo/logging"
)
var log = logging.Module("gather") // +checklocksignore
// WriteBuffer is a write buffer for content of unknown size that manages
// data in a series of byte slices of uniform size.
type WriteBuffer struct {
alloc *chunkAllocator
mu sync.Mutex
inner Bytes
}
// Close releases all memory allocated by this buffer.
func (b *WriteBuffer) Close() {
b.mu.Lock()
defer b.mu.Unlock()
if b.alloc != nil {
for _, s := range b.inner.Slices {
b.alloc.releaseChunk(s)
}
b.alloc = nil
}
b.inner.invalidate()
}
// MakeContiguous ensures the write buffer consists of exactly one contiguous single slice of the provided length
// and returns the slice.
func (b *WriteBuffer) MakeContiguous(length int) []byte {
b.Reset()
b.mu.Lock()
defer b.mu.Unlock()
var v []byte
switch {
case length <= typicalContiguousAllocator.chunkSize:
// most commonly used allocator for default chunk size with max 8MB
b.alloc = typicalContiguousAllocator
v = b.allocChunk()[0:length]
case length <= maxContiguousAllocator.chunkSize:
b.alloc = maxContiguousAllocator
v = b.allocChunk()[0:length]
default:
v = make([]byte, length)
}
b.inner.Slices = [][]byte{v}
return v
}
// Reset resets buffer back to empty.
func (b *WriteBuffer) Reset() {
b.mu.Lock()
defer b.mu.Unlock()
if b.alloc != nil {
for _, s := range b.inner.Slices {
b.alloc.releaseChunk(s)
}
}
b.inner.invalidate()
b.alloc = nil
b.inner = Bytes{}
}
// Write implements io.Writer for appending to the buffer.
func (b *WriteBuffer) Write(data []byte) (n int, err error) {
b.Append(data)
return len(data), nil
}
// AppendSectionTo appends the section of the buffer to the provided slice and returns it.
func (b *WriteBuffer) AppendSectionTo(w io.Writer, offset, size int) error {
b.mu.Lock()
defer b.mu.Unlock()
return b.inner.AppendSectionTo(w, offset, size)
}
// Length returns the combined length of all slices.
func (b *WriteBuffer) Length() int {
b.mu.Lock()
defer b.mu.Unlock()
return b.inner.Length()
}
// ToByteSlice appends all bytes to the provided slice and returns it.
func (b *WriteBuffer) ToByteSlice() []byte {
b.mu.Lock()
defer b.mu.Unlock()
return b.inner.ToByteSlice()
}
// Bytes returns inner gather.Bytes.
func (b *WriteBuffer) Bytes() Bytes {
b.mu.Lock()
defer b.mu.Unlock()
return b.inner
}
// Append appends the specified slice of bytes to the buffer.
func (b *WriteBuffer) Append(data []byte) {
b.mu.Lock()
defer b.mu.Unlock()
b.inner.assertValid()
if len(b.inner.Slices) == 0 {
b.inner.sliceBuf[0] = b.allocChunk()
b.inner.Slices = b.inner.sliceBuf[0:1]
}
for len(data) > 0 {
ndx := len(b.inner.Slices) - 1
remaining := cap(b.inner.Slices[ndx]) - len(b.inner.Slices[ndx])
if remaining == 0 {
b.inner.Slices = append(b.inner.Slices, b.allocChunk())
ndx = len(b.inner.Slices) - 1
remaining = cap(b.inner.Slices[ndx]) - len(b.inner.Slices[ndx])
}
chunkSize := remaining
if chunkSize > len(data) {
chunkSize = len(data)
}
b.inner.Slices[ndx] = append(b.inner.Slices[ndx], data[0:chunkSize]...)
data = data[chunkSize:]
}
}
func (b *WriteBuffer) allocChunk() []byte {
if b.alloc == nil {
b.alloc = defaultAllocator
}
return b.alloc.allocChunk()
}
// Dup creates a clone of the WriteBuffer.
func (b *WriteBuffer) Dup() *WriteBuffer {
dup := &WriteBuffer{}
b.mu.Lock()
defer b.mu.Unlock()
dup.alloc = b.alloc
dup.inner = FromSlice(b.inner.ToByteSlice())
return dup
}
// NewWriteBuffer creates new write buffer.
func NewWriteBuffer() *WriteBuffer {
return &WriteBuffer{}
}
// NewWriteBufferMaxContiguous creates new write buffer that will allocate large chunks.
func NewWriteBufferMaxContiguous() *WriteBuffer {
return &WriteBuffer{alloc: maxContiguousAllocator}
}