114 lines
2.7 KiB
Go
114 lines
2.7 KiB
Go
// Copyright 2022 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package buffer
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"gvisor.dev/gvisor/pkg/bits"
|
|
"gvisor.dev/gvisor/pkg/sync"
|
|
)
|
|
|
|
const (
|
|
// This is log2(baseChunkSize). This number is used to calculate which pool
|
|
// to use for a payload size by right shifting the payload size by this
|
|
// number and passing the result to MostSignificantOne64.
|
|
baseChunkSizeLog2 = 6
|
|
|
|
// This is the size of the buffers in the first pool. Each subsequent pool
|
|
// creates payloads 2^(pool index) times larger than the first pool's
|
|
// payloads.
|
|
baseChunkSize = 1 << baseChunkSizeLog2 // 64
|
|
|
|
// MaxChunkSize is largest payload size that we pool. Payloads larger than
|
|
// this will be allocated from the heap and garbage collected as normal.
|
|
MaxChunkSize = baseChunkSize << (numPools - 1) // 64k
|
|
|
|
// The number of chunk pools we have for use.
|
|
numPools = 11
|
|
)
|
|
|
|
// chunkPools is a collection of pools for payloads of different sizes. The
|
|
// size of the payloads doubles in each successive pool.
|
|
var chunkPools [numPools]sync.Pool
|
|
|
|
func init() {
|
|
for i := 0; i < numPools; i++ {
|
|
chunkSize := baseChunkSize * (1 << i)
|
|
chunkPools[i].New = func() any {
|
|
return &chunk{
|
|
data: make([]byte, chunkSize),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Precondition: 0 <= size <= maxChunkSize
|
|
func getChunkPool(size int) *sync.Pool {
|
|
idx := 0
|
|
if size > baseChunkSize {
|
|
idx = bits.MostSignificantOne64(uint64(size) >> baseChunkSizeLog2)
|
|
if size > 1<<(idx+baseChunkSizeLog2) {
|
|
idx++
|
|
}
|
|
}
|
|
if idx >= numPools {
|
|
panic(fmt.Sprintf("pool for chunk size %d does not exist", size))
|
|
}
|
|
return &chunkPools[idx]
|
|
}
|
|
|
|
// Chunk represents a slice of pooled memory.
|
|
//
|
|
// +stateify savable
|
|
type chunk struct {
|
|
chunkRefs
|
|
data []byte
|
|
}
|
|
|
|
func newChunk(size int) *chunk {
|
|
var c *chunk
|
|
if size > MaxChunkSize {
|
|
c = &chunk{
|
|
data: make([]byte, size),
|
|
}
|
|
} else {
|
|
pool := getChunkPool(size)
|
|
c = pool.Get().(*chunk)
|
|
clear(c.data)
|
|
}
|
|
c.InitRefs()
|
|
return c
|
|
}
|
|
|
|
func (c *chunk) destroy() {
|
|
if len(c.data) > MaxChunkSize {
|
|
c.data = nil
|
|
return
|
|
}
|
|
pool := getChunkPool(len(c.data))
|
|
pool.Put(c)
|
|
}
|
|
|
|
func (c *chunk) DecRef() {
|
|
c.chunkRefs.DecRef(c.destroy)
|
|
}
|
|
|
|
func (c *chunk) Clone() *chunk {
|
|
cpy := newChunk(len(c.data))
|
|
copy(cpy.data, c.data)
|
|
return cpy
|
|
}
|