Documentation
¶
Overview ¶
Package fastcdc provides high-performance, thread-safe content-defined chunking (CDC) using the FastCDC algorithm with Gear hash.
Overview ¶
FastCDC is a content-defined chunking algorithm that divides data streams into variable-size chunks based on content rather than fixed boundaries. This enables efficient deduplication and delta compression.
This implementation offers:
- High performance: >1000 MB/s throughput
- Low allocations: ~1 allocation/op (Next API) or 0 allocations/op (FindBoundary API)
- Thread-safety: No data races, safe for concurrent use
- Dual API: Convenient streaming or zero-allocation
Quick Start ¶
Simple streaming API:
chunker, _ := fastcdc.NewChunker(reader, fastcdc.WithTargetSize(64*1024))
for {
chunk, err := chunker.Next()
if err == io.EOF {
break
}
// Process chunk.Data
}
Zero-allocation API for performance-critical code:
core, _ := fastcdc.NewChunkerCore(fastcdc.WithTargetSize(64*1024))
boundary, hash, found := core.FindBoundary(data)
if found {
// Process data[:boundary]
core.Reset()
}
Algorithm ¶
This implementation uses the Gear hash algorithm, which is significantly faster than Rabin fingerprinting (3 operations/byte vs 6 operations/byte) while providing similar chunking quality.
The chunking process uses normalized chunking with two-phase boundary detection:
- Skip to minimum size (fast-forward without checking)
- Normalized region: Check with smaller mask (more aggressive cutting)
- Standard region: Check with larger mask (less aggressive cutting)
- Hard limit: Force cut at maximum size
This approach prevents excessive tiny chunks while maintaining good distribution.
Thread Safety ¶
Each chunker instance maintains its own hash table, eliminating data races. Multiple goroutines can safely use separate chunker instances concurrently. For high-throughput scenarios, use ChunkerPool to recycle instances.
Performance ¶
Benchmarked on 10 MiB random data (Apple M4):
- Next() API: ~1100 MB/s, ~1 alloc/op
- FindBoundary() API: ~1100 MB/s, 0 allocs/op
- Pool API: ~1000 MB/s, ~0.1 alloc/op
Standard deviation: ~55 KiB (well under 400 KiB target)
Index ¶
- Constants
- Variables
- type Chunk
- type Chunker
- type ChunkerCore
- func (c *ChunkerCore) FindBoundary(data []byte) (boundary int, hash uint64, found bool)
- func (c *ChunkerCore) Fingerprint() uint64
- func (c *ChunkerCore) MaxSize() uint32
- func (c *ChunkerCore) MinSize() uint32
- func (c *ChunkerCore) NormSize() uint32
- func (c *ChunkerCore) Position() uint32
- func (c *ChunkerCore) Reset()
- type ChunkerCorePool
- type ChunkerPool
- type Option
Constants ¶
const ( // DefaultMinSize is the default minimum chunk size (16 KiB). DefaultMinSize = 16 * 1024 // DefaultTargetSize is the default target chunk size (64 KiB). DefaultTargetSize = 64 * 1024 // DefaultMaxSize is the default maximum chunk size (256 KiB). DefaultMaxSize = 256 * 1024 // DefaultNormLevel is the default normalization level (2) // Determines the size of the normalization region: (targetSize - minSize) / 2^normLevel. DefaultNormLevel = 2 // DefaultBufferSize is the default internal buffer size for the streaming API (512 KiB). // This is 2x the default max chunk size, providing efficient buffering. DefaultBufferSize = 512 * 1024 )
Variables ¶
var ( // ErrInvalidMinSize is returned when minSize is 0. ErrInvalidMinSize = errors.New("minSize must be greater than 0") // ErrInvalidTargetSize is returned when targetSize is 0. ErrInvalidTargetSize = errors.New("targetSize must be greater than 0") // ErrTargetSizeTooSmall is returned when targetSize is not greater than minSize. ErrTargetSizeTooSmall = errors.New("targetSize must be greater than minSize") // ErrInvalidMaxSize is returned when maxSize is 0. ErrInvalidMaxSize = errors.New("maxSize must be greater than 0") // ErrMaxSizeTooSmall is returned when maxSize is not greater than targetSize. ErrMaxSizeTooSmall = errors.New("maxSize must be greater than targetSize") // ErrInvalidNormLevel is returned when normLevel is not between 0 and 8. ErrInvalidNormLevel = errors.New("normLevel must be between 0 and 8") // ErrInvalidBufferSize is returned when bufferSize is 0. ErrInvalidBufferSize = errors.New("bufferSize must be greater than 0") )
Functions ¶
This section is empty.
Types ¶
type Chunk ¶
type Chunk struct {
Offset uint64 // Absolute offset in the stream
Length uint32 // Chunk size in bytes
Hash uint64 // Gear fingerprint at boundary
Data []byte // Chunk data (points into internal buffer)
}
Chunk represents a content-defined chunk with its metadata.
type Chunker ¶
type Chunker struct {
// contains filtered or unexported fields
}
Chunker provides a convenient streaming API for content-defined chunking. It wraps an io.Reader and returns chunks via the Next() method.
This API allocates minimally and is suitable for most use cases. For zero-allocation performance-critical code, use ChunkerCore.
func NewChunker ¶
NewChunker creates a new Chunker that reads from the given io.Reader.
func (*Chunker) Next ¶
Next returns the next chunk from the stream. Returns io.EOF when the stream is exhausted.
The returned Chunk.Data slice is valid until the next call to Next(). If you need to keep the data, copy it to your own buffer.
type ChunkerCore ¶
type ChunkerCore struct {
// contains filtered or unexported fields
}
ChunkerCore implements zero-allocation content-defined chunking using the Gear hash algorithm. It provides a low-level FindBoundary API for performance-critical code where managing buffers manually is acceptable.
For a more convenient streaming API with minimal allocations, use Chunker instead.
func NewChunkerCore ¶
func NewChunkerCore(opts ...Option) (*ChunkerCore, error)
NewChunkerCore creates a new ChunkerCore with the given options. This is a zero-allocation API - the caller manages all buffers.
func (*ChunkerCore) FindBoundary ¶
func (c *ChunkerCore) FindBoundary(data []byte) (boundary int, hash uint64, found bool)
FindBoundary scans the provided data for a chunk boundary. It returns:
- boundary: the index of the chunk boundary (exclusive)
- hash: the final Gear hash value at the boundary
- found: true if a boundary was found, false if data exhausted
This is a zero-allocation API. The caller is responsible for:
- Providing the data buffer
- Tracking absolute position across multiple calls
- Handling data at chunk boundaries
The chunker maintains state between calls, so calling FindBoundary multiple times continues scanning from where the previous call left off.
Example usage:
core := NewChunkerCore(WithTargetSize(64*1024))
buf := make([]byte, 1*1024*1024)
for {
n, _ := reader.Read(buf)
if n == 0 {
break
}
boundary, hash, found := core.FindBoundary(buf[:n])
if found {
processChunk(buf[:boundary], hash)
// Continue with remaining data: buf[boundary:n]
}
}
FindBoundary scans the provided data for a chunk boundary. It returns:
- boundary: the index of the chunk boundary (exclusive)
- hash: the final Gear hash value at the boundary
- found: true if a boundary was found, false if data exhausted
This is a zero-allocation API. The caller is responsible for:
- Providing the data buffer
- Tracking absolute position across multiple calls
- Handling data at chunk boundaries
The chunker maintains state between calls, so calling FindBoundary multiple times continues scanning from where the previous call left off.
Example usage:
core := NewChunkerCore(WithTargetSize(64*1024))
buf := make([]byte, 1*1024*1024)
for {
n, _ := reader.Read(buf)
if n == 0 {
break
}
boundary, hash, found := core.FindBoundary(buf[:n])
if found {
processChunk(buf[:boundary], hash)
// Continue with remaining data: buf[boundary:n]
}
}
func (*ChunkerCore) Fingerprint ¶
func (c *ChunkerCore) Fingerprint() uint64
Fingerprint returns the current rolling hash value.
func (*ChunkerCore) MaxSize ¶
func (c *ChunkerCore) MaxSize() uint32
MaxSize returns the maximum chunk size.
func (*ChunkerCore) MinSize ¶
func (c *ChunkerCore) MinSize() uint32
MinSize returns the minimum chunk size.
func (*ChunkerCore) NormSize ¶
func (c *ChunkerCore) NormSize() uint32
NormSize returns the normalization boundary.
func (*ChunkerCore) Position ¶
func (c *ChunkerCore) Position() uint32
Position returns the current position within the chunk being processed. This can be used to determine how much data has been consumed.
func (*ChunkerCore) Reset ¶
func (c *ChunkerCore) Reset()
Reset resets the chunker state for processing a new stream. This allows reusing the same ChunkerCore instance.
type ChunkerCorePool ¶
type ChunkerCorePool struct {
// contains filtered or unexported fields
}
ChunkerCorePool is a pool of ChunkerCore instances for reuse.
func NewChunkerCorePool ¶
func NewChunkerCorePool(opts ...Option) (*ChunkerCorePool, error)
NewChunkerCorePool creates a new ChunkerCorePool with the given options. All chunker cores created from this pool will use these options.
func (*ChunkerCorePool) Get ¶
func (p *ChunkerCorePool) Get() (*ChunkerCore, error)
Get retrieves a ChunkerCore from the pool, or creates a new one if the pool is empty.
func (*ChunkerCorePool) Put ¶
func (p *ChunkerCorePool) Put(c *ChunkerCore)
Put returns a ChunkerCore to the pool for reuse. The core should not be used after being returned to the pool.
type ChunkerPool ¶
type ChunkerPool struct {
// contains filtered or unexported fields
}
ChunkerPool is a pool of Chunker instances for reuse in high-throughput scenarios. It reduces allocations by recycling chunkers instead of creating new ones.
func NewChunkerPool ¶
func NewChunkerPool(opts ...Option) (*ChunkerPool, error)
NewChunkerPool creates a new ChunkerPool with the given options. All chunkers created from this pool will use these options.
func (*ChunkerPool) Get ¶
func (p *ChunkerPool) Get(r io.Reader) (*Chunker, error)
Get retrieves a Chunker from the pool, or creates a new one if the pool is empty. The chunker is configured with the given reader and ready to use.
func (*ChunkerPool) Put ¶
func (p *ChunkerPool) Put(c *Chunker)
Put returns a Chunker to the pool for reuse. The chunker should not be used after being returned to the pool.
type Option ¶
type Option func(*config) error
Option is a function that configures a Chunker or ChunkerCore.
func WithBufferSize ¶
WithBufferSize sets the internal buffer size for the streaming API. Must be at least as large as maxSize.
func WithNormalization ¶
WithNormalization sets the normalization level. Level 0 disables normalization (single-mask behavior). Higher levels create a larger normalization region.
func WithSeed ¶
WithSeed sets a custom seed for the Gear hash table. Using a non-zero seed will allocate a per-instance table (2 KiB).
func WithTargetSize ¶
WithTargetSize sets the target chunk size.