blob: 7aefa99dd689892deda8d5118b4923211a2351b7 [file] [log] [blame]
package slab
import (
"sort"
"testing"
)
func TestBasics(t *testing.T) {
s := NewArena(1, 1024, 2, nil)
if s == nil {
t.Errorf("expected new slab arena to work")
}
a := s.Alloc(1)
if a == nil {
t.Errorf("expected alloc to work")
}
if len(a) != 1 {
t.Errorf("expected alloc to give right size buf")
}
if cap(a) != 1+slabMemoryFooterLen {
t.Errorf("expected alloc cap to match algorithm, got: %v vs %v",
cap(a), 1+slabMemoryFooterLen)
}
a[0] = 66
if s.DecRef(a) != true {
t.Errorf("expected DecRef to be the last one")
}
b := s.Alloc(1)
if b == nil {
t.Errorf("expected alloc to work")
}
if len(b) != 1 {
t.Errorf("expected alloc to give right size buf")
}
if cap(b) != 1+slabMemoryFooterLen {
t.Errorf("expected alloc cap to match algorithm, got: %v vs %v",
cap(b), 1+slabMemoryFooterLen)
}
if b[0] != 66 {
t.Errorf("expected alloc to return last freed buf")
}
s.AddRef(b)
if s.DecRef(b) != false {
t.Errorf("expected DecRef() to not be the last")
}
if s.DecRef(b) != true {
t.Errorf("expected DecRef() to be the last")
}
c := s.Alloc(1)
if c[0] != 66 {
t.Errorf("expected alloc to return last freed buf")
}
}
func TestSlabClassGrowth(t *testing.T) {
s := NewArena(1, 8, 2, nil)
expectSlabClasses := func(numSlabClasses int) {
if len(s.slabClasses) != numSlabClasses {
t.Errorf("expected %v slab classses, got: %v",
numSlabClasses, len(s.slabClasses))
}
}
expectSlabClasses(1)
s.Alloc(1)
expectSlabClasses(1)
s.Alloc(1)
expectSlabClasses(1)
s.Alloc(2)
expectSlabClasses(2)
s.Alloc(1)
s.Alloc(2)
expectSlabClasses(2)
s.Alloc(3)
s.Alloc(4)
expectSlabClasses(3)
s.Alloc(5)
s.Alloc(8)
expectSlabClasses(4)
}
func TestDecRef(t *testing.T) {
s := NewArena(1, 8, 2, nil)
expectSlabClasses := func(numSlabClasses int) {
if len(s.slabClasses) != numSlabClasses {
t.Errorf("expected %v slab classses, got: %v",
numSlabClasses, len(s.slabClasses))
}
}
a := make([][]byte, 128)
for j := 0; j < 100; j++ {
for i := 0; i < len(a); i++ {
a[i] = s.Alloc(i % 8)
}
for i := 0; i < len(a); i++ {
s.DecRef(a[i])
}
}
expectSlabClasses(4)
}
func TestAddRef(t *testing.T) {
s := NewArena(1, 1, 2, nil)
if !s.slabClasses[0].chunkFree.IsNil() {
t.Errorf("expected no free chunks")
}
a := s.Alloc(1)
a[0] = 123
if !s.slabClasses[0].chunkFree.IsNil() {
t.Errorf("expected no free chunks")
}
s.AddRef(a)
if !s.slabClasses[0].chunkFree.IsNil() {
t.Errorf("expected no free chunks")
}
s.DecRef(a)
if !s.slabClasses[0].chunkFree.IsNil() {
t.Errorf("expected no free chunks")
}
s.DecRef(a)
if s.slabClasses[0].chunkFree.IsNil() {
t.Errorf("expected 1 free chunk")
}
b := s.Alloc(1)
if b[0] != 123 {
t.Errorf("expected chunk to be reused")
}
}
func TestLargeAlloc(t *testing.T) {
s := NewArena(1, 1, 2, nil)
if s.Alloc(2) != nil {
t.Errorf("expected alloc larger than slab size to fail")
}
}
func TestEmptyChunk(t *testing.T) {
s := NewArena(1, 1, 2, nil)
sc := s.slabClasses[0]
if sc.chunk(nilLoc) != nil {
t.Errorf("expected empty chunk to not have a chunk()")
}
sc1, c1 := s.chunk(nilLoc)
if sc1 != nil || c1 != nil {
t.Errorf("expected empty chunk to not have a chunk()")
}
}
func TestEmptyChunkMem(t *testing.T) {
s := NewArena(1, 1, 2, nil)
sc := s.slabClasses[0]
if sc.chunkMem(nil) != nil {
t.Errorf("expected nil chunk to not have a chunk()")
}
if sc.chunkMem(&chunk{self: nilLoc}) != nil {
t.Errorf("expected empty chunk to not have a chunk()")
}
}
func TestAddRefOnAlreadyReleasedBuf(t *testing.T) {
s := NewArena(1, 1, 2, nil)
a := s.Alloc(1)
s.DecRef(a)
var err interface{}
func() {
defer func() { err = recover() }()
s.AddRef(a)
}()
if err == nil {
t.Errorf("expected panic on AddRef on already release buf")
}
}
func TestDecRefOnAlreadyReleasedBuf(t *testing.T) {
s := NewArena(1, 1, 2, nil)
a := s.Alloc(1)
s.DecRef(a)
var err interface{}
func() {
defer func() { err = recover() }()
s.DecRef(a)
}()
if err == nil {
t.Errorf("expected panic on DecRef on already release buf")
}
}
func TestPushFreeChunkOnReferencedChunk(t *testing.T) {
s := NewArena(1, 1, 2, nil)
sc := s.slabClasses[0]
var err interface{}
func() {
defer func() { err = recover() }()
sc.pushFreeChunk(&chunk{refs: 1})
}()
if err == nil {
t.Errorf("expected panic when free'ing a ref-counted chunk")
}
}
func TestPopFreeChunkOnFreeChunk(t *testing.T) {
s := NewArena(1, 1, 2, nil)
sc := s.slabClasses[0]
sc.chunkFree = nilLoc
var err interface{}
func() {
defer func() { err = recover() }()
sc.popFreeChunk()
}()
if err == nil {
t.Errorf("expected panic when popFreeChunk() on free chunk")
}
}
func TestPopFreeChunkOnReferencedFreeChunk(t *testing.T) {
s := NewArena(1, 1024, 2, nil)
s.Alloc(1)
sc := s.slabClasses[0]
sc.chunk(sc.chunkFree).refs = 1
var err interface{}
func() {
defer func() { err = recover() }()
sc.popFreeChunk()
}()
if err == nil {
t.Errorf("expected panic when popFreeChunk() on ref'ed chunk")
}
}
func TestOwns(t *testing.T) {
s := NewArena(1, 1024, 2, nil)
if s.Owns(nil) {
t.Errorf("expected false when Owns on nil buf")
}
if s.Owns(make([]byte, 1)) {
t.Errorf("expected false when Owns on small buf")
}
if s.Owns(make([]byte, 1+slabMemoryFooterLen)) {
t.Errorf("expected false whens Owns on non-magic buf")
}
if !s.Owns(s.Alloc(1)) {
t.Errorf("expected Owns on Alloc'ed buf")
}
}
func TestAddDecRefOnUnowned(t *testing.T) {
s := NewArena(1, 1024, 2, nil)
var err interface{}
func() {
defer func() { err = recover() }()
s.AddRef(make([]byte, 1000))
}()
if err == nil {
t.Errorf("expected panic when AddRef() on unowned buf")
}
err = nil
func() {
defer func() { err = recover() }()
s.DecRef(make([]byte, 1000))
}()
if err == nil {
t.Errorf("expected panic when DecRef() on unowned buf")
}
}
func TestArenaChunk(t *testing.T) {
s := NewArena(1, 100, 2, nil)
s.Alloc(1)
sc := &(s.slabClasses[0])
c := sc.popFreeChunk()
if sc.chunk(c.self) != c {
t.Errorf("expected chunk to be the same")
}
sc1, c1 := s.chunk(c.self)
if sc1 != sc || c1 != c {
t.Errorf("expected chunk to be the same")
}
}
func TestArenaChunkMem(t *testing.T) {
s := NewArena(1, 100, 2, nil)
s.Alloc(1)
sc := s.slabClasses[0]
c := sc.popFreeChunk()
if sc.chunkMem(c) == nil {
t.Errorf("expected chunkMem to be non-nil")
}
}
func TestMalloc(t *testing.T) {
mallocWorks := true
mallocCalls := 0
malloc := func(sizeNeeded int) []byte {
mallocCalls++
if mallocWorks {
return make([]byte, sizeNeeded)
}
return nil
}
mustNil := func(aaa []byte) {
if aaa != nil {
t.Errorf("expected array to be nil")
}
}
notNil := func(aaa []byte) {
if aaa == nil {
t.Errorf("expected array to be not nil")
}
}
s := NewArena(1, 4, 2, malloc)
if mallocCalls != 0 {
t.Errorf("expect no mallocs yet")
}
a := s.Alloc(1)
notNil(a)
if mallocCalls != 1 {
t.Errorf("expect 1 malloc")
}
a = s.Alloc(1)
notNil(a)
if mallocCalls != 1 {
t.Errorf("expect 1 malloc still, since we don't need another slab yet")
}
a = s.Alloc(2)
notNil(a)
if mallocCalls != 2 {
t.Errorf("expect 2 mallocs, since we need another slab")
}
a = s.Alloc(1)
notNil(a)
if mallocCalls != 2 {
t.Errorf("expect 2 malloc still, since we don't need another slab yet")
}
a = s.Alloc(1)
notNil(a)
if mallocCalls != 2 {
t.Errorf("expect 2 malloc still, since we don't need another slab yet")
}
a = s.Alloc(1)
notNil(a)
if mallocCalls != 3 {
t.Errorf("expect 3 mallocs, since we need another slab")
}
mallocWorks = false // Now we pretend to run out of memory.
a = s.Alloc(2)
notNil(a)
if mallocCalls != 3 {
t.Errorf("expect 3 mallocs, since don't need another slab yet")
}
a = s.Alloc(2)
mustNil(a)
if mallocCalls != 4 {
t.Errorf("expect 4 mallocs, since needed another slab")
}
a = s.Alloc(3)
mustNil(a)
if mallocCalls != 5 {
t.Errorf("expect 5 mallocs, since needed another slab")
}
}
func TestChaining(t *testing.T) {
testChaining(t, NewArena(1, 1, 2, nil))
testChaining(t, NewArena(1, 100, 2, nil))
}
func testChaining(t *testing.T, s *Arena) {
a := s.Alloc(1)
f := s.Alloc(1)
s.DecRef(f) // The f buf is now freed.
if s.GetNext(a) != nil {
t.Errorf("expected nil GetNext()")
}
s.SetNext(a, nil)
if s.GetNext(a) != nil {
t.Errorf("expected nil GetNext()")
}
var err interface{}
func() {
defer func() { err = recover() }()
s.GetNext(nil)
}()
if err == nil {
t.Errorf("expected panic when GetNext(nil)")
}
err = nil
func() {
defer func() { err = recover() }()
s.GetNext(make([]byte, 1))
}()
if err == nil {
t.Errorf("expected panic when GetNext(non-arena-buf)")
}
err = nil
func() {
defer func() { err = recover() }()
s.GetNext(f)
}()
if err == nil {
t.Errorf("expected panic when GetNext(already-freed-buf)")
}
err = nil
func() {
defer func() { err = recover() }()
s.SetNext(nil, make([]byte, 1))
}()
if err == nil {
t.Errorf("expected panic when SetNext(nil)")
}
err = nil
func() {
defer func() { err = recover() }()
s.SetNext(a, make([]byte, 1))
}()
if err == nil {
t.Errorf("expected panic when SetNext(non-arena-buf)")
}
err = nil
func() {
defer func() { err = recover() }()
s.SetNext(f, nil)
}()
if err == nil {
t.Errorf("expected panic when SetNext(already-freed-buf)")
}
b0 := s.Alloc(1)
b1 := s.Alloc(1)
b1[0] = 201
s.SetNext(b0, b1)
bx := s.GetNext(b0)
if bx[0] != 201 {
t.Errorf("expected chain to work")
}
s.DecRef(bx)
s.DecRef(b1)
bx = s.GetNext(b0)
if bx[0] != 201 {
t.Errorf("expected chain to still work")
}
s.DecRef(bx)
_, b0chunk := s.bufChunk(b0)
if b0chunk.refs != 1 {
t.Errorf("expected b0chunk to still be alive")
}
_, b1chunk := s.bufChunk(b1)
if b1chunk == nil {
t.Errorf("expected b1chunk to still be alive")
}
if b1chunk.refs != 1 {
t.Errorf("expected b1chunk to still be ref'ed")
}
if b0chunk.next.IsNil() {
t.Errorf("expected b0chunk to not be empty")
}
if !b1chunk.next.IsNil() {
t.Errorf("expected b1chunk to have no next")
}
s.DecRef(b0)
if b0chunk.refs != 0 {
t.Errorf("expected b0chunk to not be ref'ed")
}
if b1chunk.refs != 0 {
t.Errorf("expected b1chunk to not be ref'ed")
}
alice := s.Alloc(1)
bob := s.Alloc(1)
betty := s.Alloc(1)
_, bobChunk := s.bufChunk(bob)
_, bettyChunk := s.bufChunk(betty)
s.SetNext(alice, bob)
if bobChunk.refs != 2 {
t.Errorf("expected bob to have 2 refs")
}
if bettyChunk.refs != 1 {
t.Errorf("expected betty to have 1 ref")
}
s.DecRef(bob)
if bobChunk.refs != 1 {
t.Errorf("expected bob to have 1 ref (from alice)")
}
if bettyChunk.refs != 1 {
t.Errorf("expected betty to have 1 ref")
}
s.SetNext(alice, betty)
if bobChunk.refs != 0 {
t.Errorf("expected bob to have 0 ref's (alice dropped bob for betty)")
}
if bettyChunk.refs != 2 {
t.Errorf("expected betty to have 2 ref (1 from alice)")
}
s.DecRef(betty)
if bobChunk.refs != 0 {
t.Errorf("expected bob to have 0 ref's (alice dropped bob for betty)")
}
if bettyChunk.refs != 1 {
t.Errorf("expected betty to have 1 ref (from alice)")
}
s.DecRef(alice)
if bobChunk.refs != 0 {
t.Errorf("expected bob to have 0 ref's (alice dropped bob for betty)")
}
if bettyChunk.refs != 0 {
t.Errorf("expected betty to have 0 ref (alice dropped betty)")
}
}
func TestFindSlabClassIndex(t *testing.T) {
s := NewArena(1, 1024, 2, nil)
test := func(bufLen, idxExp int) {
idxAct := s.findSlabClassIndex(bufLen)
if idxExp != idxAct {
t.Errorf("expected slab class index: %v, got: %v, bufLen: %v",
idxExp, idxAct, bufLen)
}
}
test(0, 0)
test(1, 0)
test(2, 1)
test(3, 2)
test(4, 2)
test(5, 3)
test(256, 8)
}
func TestGrowthFactors(t *testing.T) {
for gf := 1.1; gf < 16.7; gf = gf + 0.1 {
s := NewArena(1, 1024, gf, nil)
a := s.Alloc(1024)
a[0] = 123
s.DecRef(a)
b := s.Alloc(1024)
if b[0] != 123 {
t.Errorf("expected re-used alloc mem")
}
}
}
func BenchmarkReffing(b *testing.B) {
a := NewArena(1, 1024, 2, nil)
data := a.Alloc(1)
b.ResetTimer()
for i := 0; i < b.N; i++ {
a.AddRef(data)
a.DecRef(data)
}
}
func BenchmarkAllocingSize1(b *testing.B) {
benchmarkAllocingConstant(b, NewArena(1, 1024, 2, nil), 1)
}
func BenchmarkAllocingSize128(b *testing.B) {
benchmarkAllocingConstant(b, NewArena(1, 1024, 2, nil), 128)
}
func BenchmarkAllocingSize256(b *testing.B) {
benchmarkAllocingConstant(b, NewArena(1, 1024, 2, nil), 256)
}
func benchmarkAllocingConstant(b *testing.B, a *Arena, allocSize int) {
stuff := [][]byte{}
for i := 0; i < 1024; i++ {
stuff = append(stuff, a.Alloc(allocSize))
}
for _, x := range stuff {
a.DecRef(x)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
a.DecRef(a.Alloc(allocSize))
}
}
func BenchmarkAllocingModSizes(b *testing.B) {
benchmarkAllocingFunc(b, NewArena(1, 1024, 2, nil),
func(i int) int { return i % 1024 })
}
func BenchmarkAllocingModSizesGrowthFactor1Dot1(b *testing.B) {
benchmarkAllocingFunc(b, NewArena(1, 1024, 1.1, nil),
func(i int) int { return i % 1024 })
}
func benchmarkAllocingFunc(b *testing.B, a *Arena,
allocSize func(i int) int) {
stuff := [][]byte{}
for i := 0; i < 1024; i++ {
stuff = append(stuff, a.Alloc(allocSize(i)))
}
for _, x := range stuff {
a.DecRef(x)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
a.DecRef(a.Alloc(allocSize(i)))
}
}
func TestChainingSizes(t *testing.T) {
testChainingSizes(t, NewArena(1, 100, 2, nil))
testChainingSizes(t, NewArena(1, 200, 8, nil))
}
func testChainingSizes(t *testing.T, s *Arena) {
curr := s.Alloc(91)
for i := 90; i > 0; i-- {
next := s.Alloc(100)[0:i]
s.SetNext(next, curr)
curr = next
}
i := 1
for x := curr; x != nil; x = s.GetNext(x) {
if len(x) != i {
t.Fatalf("expected len(x): %d, got: %d", i, len(x))
}
i++
}
b := s.Alloc(5)
if s.GetNext(b) != nil {
t.Fatalf("expected nil")
}
}
func TestStats(t *testing.T) {
a := NewArena(1, 1024*1024, 2, nil)
a.Alloc(3)
a.DecRef(a.Alloc(17))
a.Alloc(4096)
stats := a.Stats(map[string]int64{})
if len(stats) == 0 {
t.Errorf("expected some stats")
}
if stats["totSlabClasses"] != 13 ||
stats["totAllocs"] != 3 ||
stats["totAddRefs"] != 0 ||
stats["totDecRefs"] != 1 ||
stats["totDecRefZeroes"] != 1 ||
stats["totGetNexts"] != 0 ||
stats["totSetNexts"] != 0 ||
stats["totMallocs"] != 3 ||
stats["totMallocErrs"] != 0 ||
stats["totTooBigErrs"] != 0 ||
stats["totAddSlabErrs"] != 0 ||
stats["totPushFreeChunks"] != 1 ||
stats["totPopFreeChunks"] != 3 ||
stats["totPopFreeChunkErrs"] != 0 {
t.Errorf("expected stats did not match")
}
if stats["slabClass-000002-numChunksInUse"] != 1 ||
stats["slabClass-000005-numChunksInUse"] != 0 {
t.Errorf("expected stats did not match InUse")
}
if stats["slabClass-000012-chunkSize"] != 4096 ||
stats["slabClass-000012-numChunks"] != 256 ||
stats["slabClass-000012-numChunksFree"] != 255 ||
stats["slabClass-000012-numChunksInUse"] != 1 ||
stats["slabClass-000012-numSlabs"] != 1 {
t.Errorf("expected stats did not match slabClass 12")
}
mk := []string{}
for k := range stats {
mk = append(mk, k)
}
sort.Strings(mk)
for _, k := range mk {
t.Logf("%s = %d", k, stats[k])
}
}
func TestLoc(t *testing.T) {
loc := NilLoc()
if !loc.IsNil() {
t.Errorf("expected nil loc to be nil")
}
a := NewArena(1, 1024*1024, 2, nil)
loc = a.BufToLoc(nil)
if !loc.IsNil() {
t.Errorf("expected loc to buf to nil on bad buf")
}
b := a.Alloc(3)
if len(b) != 3 {
t.Errorf("expected len 3")
}
b[0] = 'a'
b[1] = 'b'
b[2] = 'c'
loc = a.BufToLoc(b)
if loc.IsNil() {
t.Errorf("expected non nil loc")
}
b2 := a.LocToBuf(loc)
if b2 == nil {
t.Errorf("expected loc to buf to work")
}
if len(b2) != len(b) {
t.Errorf("expected len(b2) to be len(b)")
}
if string(b) != string(b2) {
t.Errorf("expected b == b2")
}
loc = a.BufToLoc(b[0:1])
if loc.IsNil() {
t.Errorf("expected non nil loc")
}
b3 := a.LocToBuf(loc)
if b3 == nil {
t.Errorf("expected loc to buf to work")
}
if len(b3) != 1 {
t.Errorf("expected len(b2) to be len(b)")
}
if string(b3) != "a" {
t.Errorf("expected b3 to be a")
}
b4 := a.LocToBuf(NilLoc())
if b4 != nil {
t.Errorf("expected nil loc to have nil buf")
}
loc = a.BufToLoc([]byte("hello"))
if !loc.IsNil() {
t.Errorf("expected loc nil on non-arena buf")
}
loc = a.BufToLoc(b)
a.LocAddRef(loc)
a.LocDecRef(loc)
bx := a.LocToBuf(loc)
if string(bx) != "abc" {
t.Errorf("expected loc to be abc, got: %s", bx)
}
a.LocAddRef(NilLoc())
a.LocDecRef(NilLoc())
}