github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/sstable/buffer_pool.go (about)

     1  // Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package sstable
     6  
     7  import (
     8  	"github.com/cockroachdb/errors"
     9  	"github.com/cockroachdb/pebble/internal/cache"
    10  )
    11  
    12  // A bufferHandle is a handle to manually-managed memory. The handle may point
    13  // to a block in the block cache (h.Get() != nil), or a buffer that exists
    14  // outside the block cache allocated from a BufferPool (b.Valid()).
    15  type bufferHandle struct {
    16  	h cache.Handle
    17  	b Buf
    18  }
    19  
    20  // Get retrieves the underlying buffer referenced by the handle.
    21  func (bh bufferHandle) Get() []byte {
    22  	if v := bh.h.Get(); v != nil {
    23  		return v
    24  	} else if bh.b.p != nil {
    25  		return bh.b.p.pool[bh.b.i].b
    26  	}
    27  	return nil
    28  }
    29  
    30  // Release releases the buffer, either back to the block cache or BufferPool.
    31  func (bh bufferHandle) Release() {
    32  	bh.h.Release()
    33  	bh.b.Release()
    34  }
    35  
    36  // A BufferPool holds a pool of buffers for holding sstable blocks. An initial
    37  // size of the pool is provided on Init, but a BufferPool will grow to meet the
    38  // largest working set size. It'll never shrink. When a buffer is released, the
    39  // BufferPool recycles the buffer for future allocations.
    40  //
    41  // A BufferPool should only be used for short-lived allocations with
    42  // well-understood working set sizes to avoid excessive memory consumption.
    43  //
    44  // BufferPool is not thread-safe.
    45  type BufferPool struct {
    46  	// pool contains all the buffers held by the pool, including buffers that
    47  	// are in-use. For every i < len(pool): pool[i].v is non-nil.
    48  	pool []allocedBuffer
    49  }
    50  
    51  type allocedBuffer struct {
    52  	v *cache.Value
    53  	// b holds the current byte slice. It's backed by v, but may be a subslice
    54  	// of v's memory while the buffer is in-use [ len(b) ≤ len(v.Buf()) ].
    55  	//
    56  	// If the buffer is not currently in-use, b is nil. When being recycled, the
    57  	// BufferPool.Alloc will reset b to be a subslice of v.Buf().
    58  	b []byte
    59  }
    60  
    61  // Init initializes the pool with an initial working set buffer size of
    62  // `initialSize`.
    63  func (p *BufferPool) Init(initialSize int) {
    64  	*p = BufferPool{
    65  		pool: make([]allocedBuffer, 0, initialSize),
    66  	}
    67  }
    68  
    69  // initPreallocated is like Init but for internal sstable package use in
    70  // instances where a pre-allocated slice of []allocedBuffer already exists. It's
    71  // used to avoid an extra allocation initializing BufferPool.pool.
    72  func (p *BufferPool) initPreallocated(pool []allocedBuffer) {
    73  	*p = BufferPool{
    74  		pool: pool[:0],
    75  	}
    76  }
    77  
    78  // Release releases all buffers held by the pool and resets the pool to an
    79  // uninitialized state.
    80  func (p *BufferPool) Release() {
    81  	for i := range p.pool {
    82  		if p.pool[i].b != nil {
    83  			panic(errors.AssertionFailedf("Release called on a BufferPool with in-use buffers"))
    84  		}
    85  		cache.Free(p.pool[i].v)
    86  	}
    87  	*p = BufferPool{}
    88  }
    89  
    90  // Alloc allocates a new buffer of size n. If the pool already holds a buffer at
    91  // least as large as n, the pooled buffer is used instead.
    92  //
    93  // Alloc is O(MAX(N,M)) where N is the largest number of concurrently in-use
    94  // buffers allocated and M is the initialSize passed to Init.
    95  func (p *BufferPool) Alloc(n int) Buf {
    96  	unusableBufferIdx := -1
    97  	for i := 0; i < len(p.pool); i++ {
    98  		if p.pool[i].b == nil {
    99  			if len(p.pool[i].v.Buf()) >= n {
   100  				p.pool[i].b = p.pool[i].v.Buf()[:n]
   101  				return Buf{p: p, i: i}
   102  			}
   103  			unusableBufferIdx = i
   104  		}
   105  	}
   106  
   107  	// If we would need to grow the size of the pool to allocate another buffer,
   108  	// but there was a slot available occupied by a buffer that's just too
   109  	// small, replace the too-small buffer.
   110  	if len(p.pool) == cap(p.pool) && unusableBufferIdx >= 0 {
   111  		i := unusableBufferIdx
   112  		cache.Free(p.pool[i].v)
   113  		p.pool[i].v = cache.Alloc(n)
   114  		p.pool[i].b = p.pool[i].v.Buf()
   115  		return Buf{p: p, i: i}
   116  	}
   117  
   118  	// Allocate a new buffer.
   119  	v := cache.Alloc(n)
   120  	p.pool = append(p.pool, allocedBuffer{v: v, b: v.Buf()[:n]})
   121  	return Buf{p: p, i: len(p.pool) - 1}
   122  }
   123  
   124  // A Buf holds a reference to a manually-managed, pooled byte buffer.
   125  type Buf struct {
   126  	p *BufferPool
   127  	// i holds the index into p.pool where the buffer may be found. This scheme
   128  	// avoids needing to allocate the handle to the buffer on the heap at the
   129  	// cost of copying two words instead of one.
   130  	i int
   131  }
   132  
   133  // Valid returns true if the buf holds a valid buffer.
   134  func (b Buf) Valid() bool {
   135  	return b.p != nil
   136  }
   137  
   138  // Release releases the buffer back to the pool.
   139  func (b *Buf) Release() {
   140  	if b.p == nil {
   141  		return
   142  	}
   143  	// Clear the allocedBuffer's byte slice. This signals the allocated buffer
   144  	// is no longer in use and a future call to BufferPool.Alloc may reuse this
   145  	// buffer.
   146  	b.p.pool[b.i].b = nil
   147  	b.p = nil
   148  }