lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/mem/buffer.go (about)

     1  // Copyright (C) 2017  Nexedi SA and Contributors.
     2  //                     Kirill Smelkov <kirr@nexedi.com>
     3  //
     4  // This program is free software: you can Use, Study, Modify and Redistribute
     5  // it under the terms of the GNU General Public License version 3, or (at your
     6  // option) any later version, as published by the Free Software Foundation.
     7  //
     8  // You can also Link and Combine this program with other software covered by
     9  // the terms of any of the Free Software licenses or any of the Open Source
    10  // Initiative approved licenses and Convey the resulting work. Corresponding
    11  // source of such a combination shall include the source code for all other
    12  // software used.
    13  //
    14  // This program is distributed WITHOUT ANY WARRANTY; without even the implied
    15  // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    16  //
    17  // See COPYING file for full licensing terms.
    18  // See https://www.nexedi.com/licensing for rationale and options.
    19  
    20  package mem
    21  // data buffers management
    22  
    23  import (
    24  	"sync"
    25  	"sync/atomic"
    26  
    27  	"lab.nexedi.com/kirr/go123/xmath"
    28  )
    29  
    30  // Buf is reference-counted memory buffer.
    31  //
    32  // To lower pressure on Go garbage-collector allocate buffers with BufAlloc and
    33  // free them with Buf.Release.
    34  //
    35  // Custom allocation functions affect only performance, not correctness -
    36  // everything should work if data buffer is allocated and/or free'ed
    37  // via regular Go/GC-way.
    38  type Buf struct {
    39  	Data []byte
    40  
    41  	// reference counter.
    42  	//
    43  	// NOTE to allow both Bufs created via BufAlloc and via std new, Buf is
    44  	// created with refcnt=0. The real number of references to Buf is thus .refcnt+1
    45  	refcnt int32
    46  }
    47  
    48  const order0 = 4 // buf sizes start from 2^4 (=16)
    49  
    50  var bufPoolv = [19]sync.Pool{} // buf size stop at 2^(4+19-1) (=4M)
    51  
    52  
    53  func init() {
    54  	for i := 0; i < len(bufPoolv); i++ {
    55  		i := i
    56  		bufPoolv[i].New = func() interface{} {
    57  			// NOTE *Buf, not just buf, to avoid allocation when
    58  			// making interface{} from it (interface{} wants to always point to heap)
    59  			return &Buf{Data: make([]byte, 1<<(order0+uint(i)))}
    60  		}
    61  	}
    62  }
    63  
    64  // BufAlloc allocates buffer of requested size from freelist.
    65  //
    66  // buffer memory is not initialized.
    67  func BufAlloc(size int) *Buf {
    68  	return BufAlloc64(int64(size))
    69  }
    70  
    71  // BufAlloc64 is same as BufAlloc but accepts int64 for size.
    72  func BufAlloc64(size int64) *Buf {
    73  	if size < 0 {
    74  		panic("invalid size")
    75  	}
    76  
    77  	// order = min i: 2^i >= size
    78  	order := xmath.CeilLog2(uint64(size))
    79  
    80  	order -= order0
    81  	if order < 0 {
    82  		order = 0
    83  	}
    84  
    85  	// if too big - allocate straightly from heap
    86  	if order >= len(bufPoolv) {
    87  		return &Buf{Data: make([]byte, size)}
    88  	}
    89  
    90  	buf := bufPoolv[order].Get().(*Buf)
    91  	buf.Data = buf.Data[:size] // leaving cap as is = 2^i
    92  	buf.refcnt = 0
    93  	return buf
    94  }
    95  
    96  // Release marks buf as no longer used by caller.
    97  //
    98  // It decrements buf reference-counter and if it reaches zero returns buf to
    99  // freelist.
   100  //
   101  // The caller must not use buf after call to Release.
   102  func (buf *Buf) Release() {
   103  	rc := atomic.AddInt32(&buf.refcnt, -1)
   104  	if rc < 0 - 1 {
   105  		panic("Buf.Release: refcnt < 0")
   106  	}
   107  	if rc > 0 - 1 {
   108  		return
   109  	}
   110  
   111  	// order = max i: 2^i <= cap
   112  	order := xmath.FloorLog2(uint64(cap(buf.Data)))
   113  
   114  	order -= order0
   115  	if order < 0 {
   116  		return // too small
   117  	}
   118  
   119  	if order >= len(bufPoolv) {
   120  		return // too big
   121  	}
   122  
   123  	bufPoolv[order].Put(buf)
   124  }
   125  
   126  // Incref increments buf's reference counter by 1.
   127  //
   128  // buf must already have reference-counter > 0 before Incref call.
   129  func (buf *Buf) Incref() {
   130  	rc := atomic.AddInt32(&buf.refcnt, +1)
   131  	if rc <= 1 - 1 {
   132  		panic("Buf.Incref: refcnt was < 1")
   133  	}
   134  }
   135  
   136  // XRelease releases buf it is != nil.
   137  func (buf *Buf) XRelease() {
   138  	if buf != nil {
   139  		buf.Release()
   140  	}
   141  }
   142  
   143  // XIncref increments buf's reference counter by 1 if buf != nil.
   144  func (buf *Buf) XIncref() {
   145  	if buf != nil {
   146  		buf.Incref()
   147  	}
   148  }
   149  
   150  
   151  // Len returns buf's len.
   152  //
   153  // it works even if buf=nil similarly to len() on nil []byte slice.
   154  func (buf *Buf) Len() int {
   155  	if buf != nil {
   156  		return len(buf.Data)
   157  	}
   158  	return 0
   159  }
   160  
   161  // Cap returns buf's cap.
   162  //
   163  // it works even if buf=nil similarly to len() on nil []byte slice.
   164  func (buf *Buf) Cap() int {
   165  	if buf != nil {
   166  		return cap(buf.Data)
   167  	}
   168  	return 0
   169  }
   170  
   171  // XData return's buf.Data or nil if buf == nil.
   172  func (buf *Buf) XData() []byte {
   173  	if buf != nil {
   174  		return buf.Data
   175  	}
   176  	return nil
   177  }