LCOV - code coverage report
Current view: top level - pebble/sstable - buffer_pool.go (source / functions) Hit Total Coverage
Test: 2023-10-15 08:16Z bbbf3df1 - tests + meta.lcov Lines: 54 55 98.2 %
Date: 2023-10-15 08:17:48 Functions: 0 0 -

          Line data    Source code
       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           2 : func (bh bufferHandle) Get() []byte {
      22           2 :         if v := bh.h.Get(); v != nil {
      23           2 :                 return v
      24           2 :         } else if bh.b.p != nil {
      25           2 :                 return bh.b.p.pool[bh.b.i].b
      26           2 :         }
      27           2 :         return nil
      28             : }
      29             : 
      30             : // Release releases the buffer, either back to the block cache or BufferPool.
      31           2 : func (bh bufferHandle) Release() {
      32           2 :         bh.h.Release()
      33           2 :         bh.b.Release()
      34           2 : }
      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           2 : func (p *BufferPool) Init(initialSize int) {
      64           2 :         *p = BufferPool{
      65           2 :                 pool: make([]allocedBuffer, 0, initialSize),
      66           2 :         }
      67           2 : }
      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           2 : func (p *BufferPool) initPreallocated(pool []allocedBuffer) {
      73           2 :         *p = BufferPool{
      74           2 :                 pool: pool[:0],
      75           2 :         }
      76           2 : }
      77             : 
      78             : // Release releases all buffers held by the pool and resets the pool to an
      79             : // uninitialized state.
      80           2 : func (p *BufferPool) Release() {
      81           2 :         for i := range p.pool {
      82           2 :                 if p.pool[i].b != nil {
      83           0 :                         panic(errors.AssertionFailedf("Release called on a BufferPool with in-use buffers"))
      84             :                 }
      85           2 :                 cache.Free(p.pool[i].v)
      86             :         }
      87           2 :         *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           2 : func (p *BufferPool) Alloc(n int) Buf {
      96           2 :         unusableBufferIdx := -1
      97           2 :         for i := 0; i < len(p.pool); i++ {
      98           2 :                 if p.pool[i].b == nil {
      99           2 :                         if len(p.pool[i].v.Buf()) >= n {
     100           2 :                                 p.pool[i].b = p.pool[i].v.Buf()[:n]
     101           2 :                                 return Buf{p: p, i: i}
     102           2 :                         }
     103           2 :                         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           2 :         if len(p.pool) == cap(p.pool) && unusableBufferIdx >= 0 {
     111           2 :                 i := unusableBufferIdx
     112           2 :                 cache.Free(p.pool[i].v)
     113           2 :                 p.pool[i].v = cache.Alloc(n)
     114           2 :                 p.pool[i].b = p.pool[i].v.Buf()
     115           2 :                 return Buf{p: p, i: i}
     116           2 :         }
     117             : 
     118             :         // Allocate a new buffer.
     119           2 :         v := cache.Alloc(n)
     120           2 :         p.pool = append(p.pool, allocedBuffer{v: v, b: v.Buf()[:n]})
     121           2 :         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           2 : func (b Buf) Valid() bool {
     135           2 :         return b.p != nil
     136           2 : }
     137             : 
     138             : // Release releases the buffer back to the pool.
     139           2 : func (b *Buf) Release() {
     140           2 :         if b.p == nil {
     141           2 :                 return
     142           2 :         }
     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           2 :         b.p.pool[b.i].b = nil
     147           2 :         b.p = nil
     148             : }

Generated by: LCOV version 1.14