github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/buffer/chunk.go (about)

     1  // Copyright 2022 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package buffer
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/nicocha30/gvisor-ligolo/pkg/bits"
    21  	"github.com/nicocha30/gvisor-ligolo/pkg/sync"
    22  )
    23  
    24  const (
    25  	// This is log2(baseChunkSize). This number is used to calculate which pool
    26  	// to use for a payload size by right shifting the payload size by this
    27  	// number and passing the result to MostSignificantOne64.
    28  	baseChunkSizeLog2 = 6
    29  
    30  	// This is the size of the buffers in the first pool. Each subsquent pool
    31  	// creates payloads 2^(pool index) times larger than the first pool's
    32  	// payloads.
    33  	baseChunkSize = 1 << baseChunkSizeLog2 // 64
    34  
    35  	// MaxChunkSize is largest payload size that we pool. Payloads larger than
    36  	// this will be allocated from the heap and garbage collected as normal.
    37  	MaxChunkSize = baseChunkSize << (numPools - 1) // 64k
    38  
    39  	// The number of chunk pools we have for use.
    40  	numPools = 11
    41  )
    42  
    43  // chunkPools is a collection of pools for payloads of different sizes. The
    44  // size of the payloads doubles in each successive pool.
    45  var chunkPools [numPools]sync.Pool
    46  
    47  func init() {
    48  	for i := 0; i < numPools; i++ {
    49  		chunkSize := baseChunkSize * (1 << i)
    50  		chunkPools[i].New = func() any {
    51  			return &chunk{
    52  				data: make([]byte, chunkSize),
    53  			}
    54  		}
    55  	}
    56  }
    57  
    58  // Precondition: 0 <= size <= maxChunkSize
    59  func getChunkPool(size int) *sync.Pool {
    60  	idx := 0
    61  	if size > baseChunkSize {
    62  		idx = bits.MostSignificantOne64(uint64(size) >> baseChunkSizeLog2)
    63  		if size > 1<<(idx+baseChunkSizeLog2) {
    64  			idx++
    65  		}
    66  	}
    67  	if idx >= numPools {
    68  		panic(fmt.Sprintf("pool for chunk size %d does not exist", size))
    69  	}
    70  	return &chunkPools[idx]
    71  }
    72  
    73  // Chunk represents a slice of pooled memory.
    74  //
    75  // +stateify savable
    76  type chunk struct {
    77  	chunkRefs
    78  	data []byte
    79  }
    80  
    81  func newChunk(size int) *chunk {
    82  	var c *chunk
    83  	if size > MaxChunkSize {
    84  		c = &chunk{
    85  			data: make([]byte, size),
    86  		}
    87  	} else {
    88  		pool := getChunkPool(size)
    89  		c = pool.Get().(*chunk)
    90  		for i := range c.data {
    91  			c.data[i] = 0
    92  		}
    93  	}
    94  	c.InitRefs()
    95  	return c
    96  }
    97  
    98  func (c *chunk) destroy() {
    99  	if len(c.data) > MaxChunkSize {
   100  		c.data = nil
   101  		return
   102  	}
   103  	pool := getChunkPool(len(c.data))
   104  	pool.Put(c)
   105  }
   106  
   107  func (c *chunk) DecRef() {
   108  	c.chunkRefs.DecRef(c.destroy)
   109  }
   110  
   111  func (c *chunk) Clone() *chunk {
   112  	cpy := newChunk(len(c.data))
   113  	copy(cpy.data, c.data)
   114  	return cpy
   115  }