Buffer Pool

The Buffer Pool module provides memory-efficient buffer management for FlashFS operations, particularly for file hashing and I/O operations. It reduces memory allocations and garbage collection pressure by reusing byte slices.

Overview

The Buffer Pool is designed to:

  1. Provide a pool of reusable byte slices
  2. Reduce memory allocations and garbage collection overhead
  3. Optimize performance for I/O-intensive operations
  4. Track usage metrics for monitoring and tuning
  5. Support configurable buffer sizes and pool behavior

Key Components

BufferPool

The core component is the BufferPool struct, which manages a pool of byte slices:

type BufferPool struct {
    pool                 sync.Pool
    size                 int
    name                 string
    metrics              *BufferPoolMetrics
    minSize              int
    maxSize              int
    shortageNotification chan struct{}
    getTimeout           time.Duration
}

BufferPoolOptions

The behavior of a buffer pool can be configured using the BufferPoolOptions struct:

type BufferPoolOptions struct {
    BufferSize           int           // Initial and default buffer size
    MinBufferSize        int           // Minimum buffer size to accept back into the pool
    MaxBufferSize        int           // Maximum buffer size to create
    PoolName             string        // Name for the pool (for metrics/logging)
    ShortageNotification chan struct{} // Channel for communicating buffer supply shortages
    GetTimeout           time.Duration // Timeout for waiting on a buffer
}

BufferPoolMetrics

The BufferPoolMetrics struct tracks usage statistics for a buffer pool:

type BufferPoolMetrics struct {
    Gets              uint64
    Puts              uint64
    NewAllocations    uint64
    ResizeAllocations uint64
    RejectedBuffers   uint64
    Size              int
    Timeouts          uint64
    Name              string
}

Core Functions

Creating and Using a Buffer Pool

// Create a new buffer pool with custom options
options := hash.DefaultBufferPoolOptions()
options.BufferSize = 32 * 1024 * 1024 // 32MB buffers
options.PoolName = "large-file-pool"
pool := hash.NewBufferPool(options)

// Get a buffer from the pool
buffer, err := pool.Get()
if err != nil {
    // Handle error
}

// Use the buffer for I/O operations
// ...

// Return the buffer to the pool when done
pool.Put(buffer)

Using the Default Buffer Pool

FlashFS provides a default buffer pool for convenience:

// Get a buffer from the default pool
buffer, err := hash.GetBuffer()
if err != nil {
    // Handle error
}

// Use the buffer
// ...

// Return the buffer to the default pool
hash.PutBuffer(buffer)

Buffer Management

The buffer pool implements several strategies to efficiently manage memory:

  1. Buffer Reuse: Buffers are returned to the pool after use and reused for subsequent operations
  2. Size Management: Buffers that are too small are rejected, while oversized buffers are trimmed
  3. Timeout Handling: Optional timeouts prevent blocking when buffers are scarce
  4. Shortage Notification: Optional channel notifications when buffer demand exceeds supply

Performance Considerations

  • Buffer Size: Larger buffers improve throughput for sequential I/O but consume more memory
  • Pool Size: The pool automatically grows and shrinks based on demand
  • Concurrency: The buffer pool is thread-safe and can be used from multiple goroutines
  • Memory Pressure: Monitor metrics to detect memory pressure and adjust buffer sizes accordingly

Metrics and Monitoring

The buffer pool provides detailed metrics to help monitor and tune performance:

// Get metrics from a buffer pool
metrics := pool.Metrics()
fmt.Println(metrics.String())

// Output:
// BufferPool 'large-file-pool' Metrics:
//   Gets:               1250
//   Puts:               1248
//   New Allocations:    10
//   Resize Allocations: 2
//   Rejected Buffers:   5
//   Size:               33554432
//   Timeouts:           0

Example Usage

// Create a custom buffer pool for large file operations
largeFilePool := hash.NewBufferPool(hash.BufferPoolOptions{
    BufferSize:    64 * 1024 * 1024, // 64MB
    MinBufferSize: 32 * 1024 * 1024, // 32MB
    MaxBufferSize: 128 * 1024 * 1024, // 128MB
    PoolName:      "large-file-pool",
    GetTimeout:    5 * time.Second,
})

// Process multiple files using the pool
for _, filePath := range filePaths {
    // Get a buffer from the pool
    buffer, err := largeFilePool.Get()
    if err != nil {
        log.Printf("Failed to get buffer: %v", err)
        continue
    }

    // Use the buffer for file processing
    file, err := os.Open(filePath)
    if err != nil {
        largeFilePool.Put(buffer) // Return buffer to pool
        log.Printf("Failed to open file: %v", err)
        continue
    }

    // Process the file using the buffer
    // ...

    file.Close()

    // Return the buffer to the pool
    largeFilePool.Put(buffer)
}

// Print metrics
fmt.Println(largeFilePool.Metrics().String())

Integration with Hash Module

The Buffer Pool is tightly integrated with the Hash module to optimize memory usage during hashing operations:

// The hash.File function automatically uses the buffer pool
result := hash.File(path, hash.DefaultOptions())

// The hash.Reader function also uses the buffer pool
result := hash.Reader(reader, hash.BLAKE3)