memstore/list/slab.go (298 lines of code) (raw):
// Copyright (c) 2013 Couchbase, Inc.
// 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 list
// #include "string.h"
import "C"
import (
"fmt"
"math"
"math/rand"
"sort"
"unsafe"
)
// An opaque reference to bytes managed by an Arena. See
// Arena.BufToLoc/LocToBuf(). A Loc struct is GC friendly in that a
// Loc does not have direct pointer fields into the Arena's memoryOffset
// that the GC's scanner must traverse.
type Loc struct {
slabClassIndex int
slabIndex int
chunkIndex int
bufStart int
bufLen int
}
// NilLoc returns a Loc where Loc.IsNil() is true.
func NilLoc() Loc {
return nilLoc
}
var nilLoc = Loc{-1, -1, -1, -1, -1} // A sentinel.
var nilAddr = [2]uintptr{0, 0}
func IsNilAddr(addr [2]uintptr) bool {
return addr == nilAddr
}
// IsNil returns true if the Loc came from NilLoc().
func (cl Loc) IsNil() bool {
return cl.slabClassIndex < 0 && cl.slabIndex < 0 &&
cl.chunkIndex < 0 && cl.bufStart < 0 && cl.bufLen < 0
}
// An Arena manages a set of slab classes and memoryOffset.
type Arena struct {
mp NativeMemoryPool
growthFactor float64
slabClasses []slabClass // slabClasses's chunkSizes grow by growthFactor.
slabMagic int32 // Magic # suffix on each slab memoryOffset []byte.
slabSize int
totAllocs int64
totAddRefs int64
totDecRefs int64
totDecRefZeroes int64 // Inc'ed when a ref-count reaches zero.
totGetNexts int64
totSetNexts int64
totMallocs int64
totMallocErrs int64
totTooBigErrs int64
totAddSlabErrs int64
totPushFreeChunks int64 // Inc'ed when chunk added to free list.
totPopFreeChunks int64 // Inc'ed when chunk removed from free list.
totPopFreeChunkErrs int64
}
type slabClass struct {
slabs []*slab // A growing array of slabs.
chunkSize int // Each slab is sliced into fixed-sized chunks.
chunkFree Loc // Chunks are tracked in a free-list per slabClass.
numChunks int64
numChunksFree int64
}
type slab struct {
// offset to arena.baseAddr
memoryOffset uintptr
// length of the memory allocated to this slab.
length int
// Matching array of chunk metadata, and len(memoryOffset) == len(chunks).
chunks []chunk
}
// Based on slabClassIndex + slabIndex + slabMagic.
const slabMemoryFooterLen int = 4 + 4 + 4
type chunk struct {
refs int32 // Ref-count.
self Loc // The self is the Loc for this chunk.
next Loc // Used when chunk is in the free-list or when chained.
}
// NewArena returns an Arena to manage byte slice memoryOffset based on a
// slab allocator approach.
//
// The startChunkSize and slabSize should be > 0.
// The growthFactor should be > 1.0.
func NewArena(startChunkSize int, slabSize int, growthFactor float64,
mp NativeMemoryPool) *Arena {
s := &Arena{
growthFactor: growthFactor,
slabMagic: rand.Int31(),
slabSize: slabSize,
mp: mp,
}
s.addSlabClass(startChunkSize)
return s
}
// Alloc may return nil on errors, such as if no more free chunks are
// available and new slab memoryOffset was not allocatable (such as if
// malloc() returns nil). The returned buf may not be append()'ed to
// for growth. The returned buf must be DecRef()'ed for memoryOffset reuse.
func (s *Arena) Alloc(bufLen int) [2]uintptr {
sc, chunk := s.allocChunk(bufLen)
if sc == nil || chunk == nil {
return [2]uintptr{0, 0}
}
return sc.chunkMem(chunk)
}
// Owns returns true if this Arena owns the buf.
func (s *Arena) Owns(offsets [2]uintptr) bool {
sc, c := s.bufChunk(offsets)
return sc != nil && c != nil
}
// AddRef increase the ref count on a buf. The input buf must be from
// an Alloc() from the same Arena.
func (s *Arena) AddRef(offsets [2]uintptr) {
s.totAddRefs++
sc, c := s.bufChunk(offsets)
if sc == nil || c == nil {
panic("buf not from this arena")
}
c.addRef()
}
// DecRef decreases the ref count on a buf. The input buf must be
// from an Alloc() from the same Arena. Once the buf's ref-count
// drops to 0, the Arena may reuse the buf. Returns true if this was
// the last DecRef() invocation (ref count reached 0).
func (s *Arena) DecRef(buf [2]uintptr) bool {
s.totDecRefs++
sc, c := s.bufChunk(buf)
if sc == nil || c == nil {
panic("buf not from this arena")
}
return s.decRef(sc, c)
}
// ---------------------------------------------------------------
func (s *Arena) allocChunk(bufLen int) (*slabClass, *chunk) {
s.totAllocs++
if bufLen > s.slabSize {
s.totTooBigErrs++
return nil, nil
}
slabClassIndex := s.findSlabClassIndex(bufLen)
sc := &(s.slabClasses[slabClassIndex])
if sc.chunkFree.IsNil() {
if !s.addSlab(slabClassIndex, s.slabSize, s.slabMagic) {
s.totAddSlabErrs++
return nil, nil
}
}
s.totPopFreeChunks++
chunk := sc.popFreeChunk()
if chunk == nil {
s.totPopFreeChunkErrs++
return nil, nil
}
return sc, chunk
}
func (s *Arena) findSlabClassIndex(bufLen int) int {
i := sort.Search(len(s.slabClasses),
func(i int) bool { return bufLen <= s.slabClasses[i].chunkSize })
if i >= len(s.slabClasses) {
for {
slabClass := &(s.slabClasses[len(s.slabClasses)-1])
nextChunkSize := int(
math.Ceil(float64(slabClass.chunkSize) * s.growthFactor))
s.addSlabClass(nextChunkSize)
if bufLen <= nextChunkSize {
return len(s.slabClasses) - 1
}
}
}
return i
}
func (s *Arena) addSlabClass(chunkSize int) {
s.slabClasses = append(s.slabClasses, slabClass{
chunkSize: chunkSize,
chunkFree: nilLoc,
})
}
func (s *Arena) addSlab(
slabClassIndex, slabSize int, slabMagic int32) bool {
sc := &(s.slabClasses[slabClassIndex])
chunksPerSlab := slabSize / sc.chunkSize
if chunksPerSlab <= 0 {
chunksPerSlab = 1
}
slabIndex := len(sc.slabs)
s.totMallocs++
// Re-multiplying to avoid any extra fractional chunk memoryOffset.
memorySize := (sc.chunkSize * chunksPerSlab) + slabMemoryFooterLen
memoryOffset := s.mp.Malloc(memorySize)
slab := &slab{
memoryOffset: uintptr(memoryOffset),
length: memorySize,
chunks: make([]chunk, chunksPerSlab),
}
memoryAddr := s.mp.GetBaseAddr() + slab.memoryOffset + uintptr(slab.length-slabMemoryFooterLen)
*(*uint32)(unsafe.Pointer(memoryAddr)) = uint32(slabClassIndex)
*(*uint32)(unsafe.Pointer(memoryAddr + uintptr(4))) = uint32(slabIndex)
*(*uint32)(unsafe.Pointer(memoryAddr + uintptr(8))) = uint32(slabMagic)
sc.slabs = append(sc.slabs, slab)
for i := 0; i < len(slab.chunks); i++ {
c := &(slab.chunks[i])
c.self.slabClassIndex = slabClassIndex
c.self.slabIndex = slabIndex
c.self.chunkIndex = i
c.self.bufStart = 0
c.self.bufLen = sc.chunkSize
sc.pushFreeChunk(c)
}
sc.numChunks += int64(len(slab.chunks))
return true
}
func (sc *slabClass) pushFreeChunk(c *chunk) {
if c.refs != 0 {
panic(fmt.Sprintf("pushFreeChunk() non-zero refs: %v", c.refs))
}
c.next = sc.chunkFree
sc.chunkFree = c.self
sc.numChunksFree++
}
func (sc *slabClass) popFreeChunk() *chunk {
if sc.chunkFree.IsNil() {
panic("popFreeChunk() when chunkFree is nil")
}
c := sc.chunk(sc.chunkFree)
if c.refs != 0 {
panic(fmt.Sprintf("popFreeChunk() non-zero refs: %v", c.refs))
}
c.refs = 1
sc.chunkFree = c.next
c.next = nilLoc
sc.numChunksFree--
if sc.numChunksFree < 0 {
panic("popFreeChunk() got < 0 numChunksFree")
}
return c
}
// chunkMem returns the offset of the memory address along with the chunk end offset.
// zero footer offset means invalid block.
func (sc *slabClass) chunkMem(c *chunk) [2]uintptr {
if c == nil || c.self.IsNil() {
return [2]uintptr{0, 0}
}
beg := uintptr(sc.chunkSize * c.self.chunkIndex)
offset := sc.slabs[c.self.slabIndex].memoryOffset + beg
chuckEndOffset := sc.slabs[c.self.slabIndex].memoryOffset + uintptr(sc.slabs[c.self.slabIndex].length)
return [2]uintptr{offset, chuckEndOffset}
}
func (sc *slabClass) chunk(cl Loc) *chunk {
if cl.IsNil() {
return nil
}
return &(sc.slabs[cl.slabIndex].chunks[cl.chunkIndex])
}
func (s *Arena) chunk(cl Loc) (*slabClass, *chunk) {
if cl.IsNil() {
return nil, nil
}
sc := &(s.slabClasses[cl.slabClassIndex])
return sc, sc.chunk(cl)
}
// Determine the slabClass & chunk for an Arena managed buf []byte.
func (s *Arena) bufChunk(offsets [2]uintptr) (*slabClass, *chunk) {
offset := offsets[0]
endOffset := offsets[1]
if int(endOffset-offset) <= slabMemoryFooterLen {
return nil, nil
}
footerDist := endOffset - offset - uintptr(slabMemoryFooterLen)
footerAddr := s.mp.GetBaseAddr() + offset + footerDist
slabClassIndex := *(*uint32)(unsafe.Pointer(footerAddr))
slabIndex := *(*uint32)(unsafe.Pointer(footerAddr + uintptr(4)))
slabMagic := *(*uint32)(unsafe.Pointer(footerAddr + uintptr(8)))
if slabMagic != uint32(s.slabMagic) {
return nil, nil
}
sc := &(s.slabClasses[slabClassIndex])
slab := sc.slabs[slabIndex]
chunkIndex := len(slab.chunks) -
int(math.Ceil(float64(footerDist)/float64(sc.chunkSize)))
return sc, &(slab.chunks[chunkIndex])
}
func (c *chunk) addRef() *chunk {
c.refs++
if c.refs <= 1 {
panic(fmt.Sprintf("refs <= 1 during addRef: %#v", c))
}
return c
}
func (s *Arena) decRef(sc *slabClass, c *chunk) bool {
c.refs--
if c.refs < 0 {
panic(fmt.Sprintf("refs < 0 during decRef: %#v", c))
}
if c.refs == 0 {
s.totDecRefZeroes++
scNext, cNext := s.chunk(c.next)
if scNext != nil && cNext != nil {
s.decRef(scNext, cNext)
}
c.next = nilLoc
s.totPushFreeChunks++
sc.pushFreeChunk(c)
return true
}
return false
}
// Stats fills an input map with runtime metrics about the Arena.
func (s *Arena) Stats(m map[string]int64) map[string]int64 {
m["totSlabClasses"] = int64(len(s.slabClasses))
m["totAllocs"] = s.totAllocs
m["totAddRefs"] = s.totAddRefs
m["totDecRefs"] = s.totDecRefs
m["totDecRefZeroes"] = s.totDecRefZeroes
m["totGetNexts"] = s.totGetNexts
m["totSetNexts"] = s.totSetNexts
m["totMallocs"] = s.totMallocs
m["totMallocErrs"] = s.totMallocErrs
m["totTooBigErrs"] = s.totTooBigErrs
m["totAddSlabErrs"] = s.totAddSlabErrs
m["totPushFreeChunks"] = s.totPushFreeChunks
m["totPopFreeChunks"] = s.totPopFreeChunks
m["totPopFreeChunkErrs"] = s.totPopFreeChunkErrs
for i, sc := range s.slabClasses {
prefix := fmt.Sprintf("slabClass-%06d-", i)
m[prefix+"numSlabs"] = int64(len(sc.slabs))
m[prefix+"chunkSize"] = int64(sc.chunkSize)
m[prefix+"numChunks"] = int64(sc.numChunks)
m[prefix+"numChunksFree"] = int64(sc.numChunksFree)
m[prefix+"numChunksInUse"] = int64(sc.numChunks - sc.numChunksFree)
}
return m
}