551 lines
14 KiB
Go
551 lines
14 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package runtime_test
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
. "runtime"
|
|
"testing"
|
|
)
|
|
|
|
// Ensures that got and want are the same, and if not, reports
|
|
// detailed diff information.
|
|
func checkPallocBits(t *testing.T, got, want *PallocBits) bool {
|
|
d := DiffPallocBits(got, want)
|
|
if len(d) != 0 {
|
|
t.Errorf("%d range(s) different", len(d))
|
|
for _, bits := range d {
|
|
t.Logf("\t@ bit index %d", bits.I)
|
|
t.Logf("\t| got: %s", StringifyPallocBits(got, bits))
|
|
t.Logf("\t| want: %s", StringifyPallocBits(want, bits))
|
|
}
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// makePallocBits produces an initialized PallocBits by setting
|
|
// the ranges in s to 1 and the rest to zero.
|
|
func makePallocBits(s []BitRange) *PallocBits {
|
|
b := new(PallocBits)
|
|
for _, v := range s {
|
|
b.AllocRange(v.I, v.N)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// Ensures that PallocBits.AllocRange works, which is a fundamental
|
|
// method used for testing and initialization since it's used by
|
|
// makePallocBits.
|
|
func TestPallocBitsAllocRange(t *testing.T) {
|
|
test := func(t *testing.T, i, n uint, want *PallocBits) {
|
|
checkPallocBits(t, makePallocBits([]BitRange{{i, n}}), want)
|
|
}
|
|
t.Run("OneLow", func(t *testing.T) {
|
|
want := new(PallocBits)
|
|
want[0] = 0x1
|
|
test(t, 0, 1, want)
|
|
})
|
|
t.Run("OneHigh", func(t *testing.T) {
|
|
want := new(PallocBits)
|
|
want[PallocChunkPages/64-1] = 1 << 63
|
|
test(t, PallocChunkPages-1, 1, want)
|
|
})
|
|
t.Run("Inner", func(t *testing.T) {
|
|
want := new(PallocBits)
|
|
want[2] = 0x3e
|
|
test(t, 129, 5, want)
|
|
})
|
|
t.Run("Aligned", func(t *testing.T) {
|
|
want := new(PallocBits)
|
|
want[2] = ^uint64(0)
|
|
want[3] = ^uint64(0)
|
|
test(t, 128, 128, want)
|
|
})
|
|
t.Run("Begin", func(t *testing.T) {
|
|
want := new(PallocBits)
|
|
want[0] = ^uint64(0)
|
|
want[1] = ^uint64(0)
|
|
want[2] = ^uint64(0)
|
|
want[3] = ^uint64(0)
|
|
want[4] = ^uint64(0)
|
|
want[5] = 0x1
|
|
test(t, 0, 321, want)
|
|
})
|
|
t.Run("End", func(t *testing.T) {
|
|
want := new(PallocBits)
|
|
want[PallocChunkPages/64-1] = ^uint64(0)
|
|
want[PallocChunkPages/64-2] = ^uint64(0)
|
|
want[PallocChunkPages/64-3] = ^uint64(0)
|
|
want[PallocChunkPages/64-4] = 1 << 63
|
|
test(t, PallocChunkPages-(64*3+1), 64*3+1, want)
|
|
})
|
|
t.Run("All", func(t *testing.T) {
|
|
want := new(PallocBits)
|
|
for i := range want {
|
|
want[i] = ^uint64(0)
|
|
}
|
|
test(t, 0, PallocChunkPages, want)
|
|
})
|
|
}
|
|
|
|
// Inverts every bit in the PallocBits.
|
|
func invertPallocBits(b *PallocBits) {
|
|
for i := range b {
|
|
b[i] = ^b[i]
|
|
}
|
|
}
|
|
|
|
// Ensures two packed summaries are identical, and reports a detailed description
|
|
// of the difference if they're not.
|
|
func checkPallocSum(t testing.TB, got, want PallocSum) {
|
|
if got.Start() != want.Start() {
|
|
t.Errorf("inconsistent start: got %d, want %d", got.Start(), want.Start())
|
|
}
|
|
if got.Max() != want.Max() {
|
|
t.Errorf("inconsistent max: got %d, want %d", got.Max(), want.Max())
|
|
}
|
|
if got.End() != want.End() {
|
|
t.Errorf("inconsistent end: got %d, want %d", got.End(), want.End())
|
|
}
|
|
}
|
|
|
|
func TestMallocBitsPopcntRange(t *testing.T) {
|
|
type test struct {
|
|
i, n uint // bit range to popcnt over.
|
|
want uint // expected popcnt result on that range.
|
|
}
|
|
tests := map[string]struct {
|
|
init []BitRange // bit ranges to set to 1 in the bitmap.
|
|
tests []test // a set of popcnt tests to run over the bitmap.
|
|
}{
|
|
"None": {
|
|
tests: []test{
|
|
{0, 1, 0},
|
|
{5, 3, 0},
|
|
{2, 11, 0},
|
|
{PallocChunkPages/4 + 1, PallocChunkPages / 2, 0},
|
|
{0, PallocChunkPages, 0},
|
|
},
|
|
},
|
|
"All": {
|
|
init: []BitRange{{0, PallocChunkPages}},
|
|
tests: []test{
|
|
{0, 1, 1},
|
|
{5, 3, 3},
|
|
{2, 11, 11},
|
|
{PallocChunkPages/4 + 1, PallocChunkPages / 2, PallocChunkPages / 2},
|
|
{0, PallocChunkPages, PallocChunkPages},
|
|
},
|
|
},
|
|
"Half": {
|
|
init: []BitRange{{PallocChunkPages / 2, PallocChunkPages / 2}},
|
|
tests: []test{
|
|
{0, 1, 0},
|
|
{5, 3, 0},
|
|
{2, 11, 0},
|
|
{PallocChunkPages/2 - 1, 1, 0},
|
|
{PallocChunkPages / 2, 1, 1},
|
|
{PallocChunkPages/2 + 10, 1, 1},
|
|
{PallocChunkPages/2 - 1, 2, 1},
|
|
{PallocChunkPages / 4, PallocChunkPages / 4, 0},
|
|
{PallocChunkPages / 4, PallocChunkPages/4 + 1, 1},
|
|
{PallocChunkPages/4 + 1, PallocChunkPages / 2, PallocChunkPages/4 + 1},
|
|
{0, PallocChunkPages, PallocChunkPages / 2},
|
|
},
|
|
},
|
|
"OddBound": {
|
|
init: []BitRange{{0, 111}},
|
|
tests: []test{
|
|
{0, 1, 1},
|
|
{5, 3, 3},
|
|
{2, 11, 11},
|
|
{110, 2, 1},
|
|
{99, 50, 12},
|
|
{110, 1, 1},
|
|
{111, 1, 0},
|
|
{99, 1, 1},
|
|
{120, 1, 0},
|
|
{PallocChunkPages / 2, PallocChunkPages / 2, 0},
|
|
{0, PallocChunkPages, 111},
|
|
},
|
|
},
|
|
"Scattered": {
|
|
init: []BitRange{
|
|
{1, 3}, {5, 1}, {7, 1}, {10, 2}, {13, 1}, {15, 4},
|
|
{21, 1}, {23, 1}, {26, 2}, {30, 5}, {36, 2}, {40, 3},
|
|
{44, 6}, {51, 1}, {53, 2}, {58, 3}, {63, 1}, {67, 2},
|
|
{71, 10}, {84, 1}, {89, 7}, {99, 2}, {103, 1}, {107, 2},
|
|
{111, 1}, {113, 1}, {115, 1}, {118, 1}, {120, 2}, {125, 5},
|
|
},
|
|
tests: []test{
|
|
{0, 11, 6},
|
|
{0, 64, 39},
|
|
{13, 64, 40},
|
|
{64, 64, 34},
|
|
{0, 128, 73},
|
|
{1, 128, 74},
|
|
{0, PallocChunkPages, 75},
|
|
},
|
|
},
|
|
}
|
|
for name, v := range tests {
|
|
v := v
|
|
t.Run(name, func(t *testing.T) {
|
|
b := makePallocBits(v.init)
|
|
for _, h := range v.tests {
|
|
if got := b.PopcntRange(h.i, h.n); got != h.want {
|
|
t.Errorf("bad popcnt (i=%d, n=%d): got %d, want %d", h.i, h.n, got, h.want)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Ensures computing bit summaries works as expected by generating random
|
|
// bitmaps and checking against a reference implementation.
|
|
func TestPallocBitsSummarizeRandom(t *testing.T) {
|
|
b := new(PallocBits)
|
|
for i := 0; i < 1000; i++ {
|
|
// Randomize bitmap.
|
|
for i := range b {
|
|
b[i] = rand.Uint64()
|
|
}
|
|
// Check summary against reference implementation.
|
|
checkPallocSum(t, b.Summarize(), SummarizeSlow(b))
|
|
}
|
|
}
|
|
|
|
// Ensures computing bit summaries works as expected.
|
|
func TestPallocBitsSummarize(t *testing.T) {
|
|
var emptySum = PackPallocSum(PallocChunkPages, PallocChunkPages, PallocChunkPages)
|
|
type test struct {
|
|
free []BitRange // Ranges of free (zero) bits.
|
|
hits []PallocSum
|
|
}
|
|
tests := make(map[string]test)
|
|
tests["NoneFree"] = test{
|
|
free: []BitRange{},
|
|
hits: []PallocSum{
|
|
PackPallocSum(0, 0, 0),
|
|
},
|
|
}
|
|
tests["OnlyStart"] = test{
|
|
free: []BitRange{{0, 10}},
|
|
hits: []PallocSum{
|
|
PackPallocSum(10, 10, 0),
|
|
},
|
|
}
|
|
tests["OnlyEnd"] = test{
|
|
free: []BitRange{{PallocChunkPages - 40, 40}},
|
|
hits: []PallocSum{
|
|
PackPallocSum(0, 40, 40),
|
|
},
|
|
}
|
|
tests["StartAndEnd"] = test{
|
|
free: []BitRange{{0, 11}, {PallocChunkPages - 23, 23}},
|
|
hits: []PallocSum{
|
|
PackPallocSum(11, 23, 23),
|
|
},
|
|
}
|
|
tests["StartMaxEnd"] = test{
|
|
free: []BitRange{{0, 4}, {50, 100}, {PallocChunkPages - 4, 4}},
|
|
hits: []PallocSum{
|
|
PackPallocSum(4, 100, 4),
|
|
},
|
|
}
|
|
tests["OnlyMax"] = test{
|
|
free: []BitRange{{1, 20}, {35, 241}, {PallocChunkPages - 50, 30}},
|
|
hits: []PallocSum{
|
|
PackPallocSum(0, 241, 0),
|
|
},
|
|
}
|
|
tests["MultiMax"] = test{
|
|
free: []BitRange{{35, 2}, {40, 5}, {100, 5}},
|
|
hits: []PallocSum{
|
|
PackPallocSum(0, 5, 0),
|
|
},
|
|
}
|
|
tests["One"] = test{
|
|
free: []BitRange{{2, 1}},
|
|
hits: []PallocSum{
|
|
PackPallocSum(0, 1, 0),
|
|
},
|
|
}
|
|
tests["AllFree"] = test{
|
|
free: []BitRange{{0, PallocChunkPages}},
|
|
hits: []PallocSum{
|
|
emptySum,
|
|
},
|
|
}
|
|
for name, v := range tests {
|
|
v := v
|
|
t.Run(name, func(t *testing.T) {
|
|
b := makePallocBits(v.free)
|
|
// In the PallocBits we create 1's represent free spots, but in our actual
|
|
// PallocBits 1 means not free, so invert.
|
|
invertPallocBits(b)
|
|
for _, h := range v.hits {
|
|
checkPallocSum(t, b.Summarize(), h)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Benchmarks how quickly we can summarize a PallocBits.
|
|
func BenchmarkPallocBitsSummarize(b *testing.B) {
|
|
patterns := []uint64{
|
|
0,
|
|
^uint64(0),
|
|
0xaa,
|
|
0xaaaaaaaaaaaaaaaa,
|
|
0x80000000aaaaaaaa,
|
|
0xaaaaaaaa00000001,
|
|
0xbbbbbbbbbbbbbbbb,
|
|
0x80000000bbbbbbbb,
|
|
0xbbbbbbbb00000001,
|
|
0xcccccccccccccccc,
|
|
0x4444444444444444,
|
|
0x4040404040404040,
|
|
0x4000400040004000,
|
|
0x1000404044ccaaff,
|
|
}
|
|
for _, p := range patterns {
|
|
buf := new(PallocBits)
|
|
for i := 0; i < len(buf); i++ {
|
|
buf[i] = p
|
|
}
|
|
b.Run(fmt.Sprintf("Unpacked%02X", p), func(b *testing.B) {
|
|
checkPallocSum(b, buf.Summarize(), SummarizeSlow(buf))
|
|
for i := 0; i < b.N; i++ {
|
|
buf.Summarize()
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Ensures page allocation works.
|
|
func TestPallocBitsAlloc(t *testing.T) {
|
|
tests := map[string]struct {
|
|
before []BitRange
|
|
after []BitRange
|
|
npages uintptr
|
|
hits []uint
|
|
}{
|
|
"AllFree1": {
|
|
npages: 1,
|
|
hits: []uint{0, 1, 2, 3, 4, 5},
|
|
after: []BitRange{{0, 6}},
|
|
},
|
|
"AllFree2": {
|
|
npages: 2,
|
|
hits: []uint{0, 2, 4, 6, 8, 10},
|
|
after: []BitRange{{0, 12}},
|
|
},
|
|
"AllFree5": {
|
|
npages: 5,
|
|
hits: []uint{0, 5, 10, 15, 20},
|
|
after: []BitRange{{0, 25}},
|
|
},
|
|
"AllFree64": {
|
|
npages: 64,
|
|
hits: []uint{0, 64, 128},
|
|
after: []BitRange{{0, 192}},
|
|
},
|
|
"AllFree65": {
|
|
npages: 65,
|
|
hits: []uint{0, 65, 130},
|
|
after: []BitRange{{0, 195}},
|
|
},
|
|
"SomeFree64": {
|
|
before: []BitRange{{0, 32}, {64, 32}, {100, PallocChunkPages - 100}},
|
|
npages: 64,
|
|
hits: []uint{^uint(0)},
|
|
after: []BitRange{{0, 32}, {64, 32}, {100, PallocChunkPages - 100}},
|
|
},
|
|
"NoneFree1": {
|
|
before: []BitRange{{0, PallocChunkPages}},
|
|
npages: 1,
|
|
hits: []uint{^uint(0), ^uint(0)},
|
|
after: []BitRange{{0, PallocChunkPages}},
|
|
},
|
|
"NoneFree2": {
|
|
before: []BitRange{{0, PallocChunkPages}},
|
|
npages: 2,
|
|
hits: []uint{^uint(0), ^uint(0)},
|
|
after: []BitRange{{0, PallocChunkPages}},
|
|
},
|
|
"NoneFree5": {
|
|
before: []BitRange{{0, PallocChunkPages}},
|
|
npages: 5,
|
|
hits: []uint{^uint(0), ^uint(0)},
|
|
after: []BitRange{{0, PallocChunkPages}},
|
|
},
|
|
"NoneFree65": {
|
|
before: []BitRange{{0, PallocChunkPages}},
|
|
npages: 65,
|
|
hits: []uint{^uint(0), ^uint(0)},
|
|
after: []BitRange{{0, PallocChunkPages}},
|
|
},
|
|
"ExactFit1": {
|
|
before: []BitRange{{0, PallocChunkPages/2 - 3}, {PallocChunkPages/2 - 2, PallocChunkPages/2 + 2}},
|
|
npages: 1,
|
|
hits: []uint{PallocChunkPages/2 - 3, ^uint(0)},
|
|
after: []BitRange{{0, PallocChunkPages}},
|
|
},
|
|
"ExactFit2": {
|
|
before: []BitRange{{0, PallocChunkPages/2 - 3}, {PallocChunkPages/2 - 1, PallocChunkPages/2 + 1}},
|
|
npages: 2,
|
|
hits: []uint{PallocChunkPages/2 - 3, ^uint(0)},
|
|
after: []BitRange{{0, PallocChunkPages}},
|
|
},
|
|
"ExactFit5": {
|
|
before: []BitRange{{0, PallocChunkPages/2 - 3}, {PallocChunkPages/2 + 2, PallocChunkPages/2 - 2}},
|
|
npages: 5,
|
|
hits: []uint{PallocChunkPages/2 - 3, ^uint(0)},
|
|
after: []BitRange{{0, PallocChunkPages}},
|
|
},
|
|
"ExactFit65": {
|
|
before: []BitRange{{0, PallocChunkPages/2 - 31}, {PallocChunkPages/2 + 34, PallocChunkPages/2 - 34}},
|
|
npages: 65,
|
|
hits: []uint{PallocChunkPages/2 - 31, ^uint(0)},
|
|
after: []BitRange{{0, PallocChunkPages}},
|
|
},
|
|
"SomeFree161": {
|
|
before: []BitRange{{0, 185}, {331, 1}},
|
|
npages: 161,
|
|
hits: []uint{332},
|
|
after: []BitRange{{0, 185}, {331, 162}},
|
|
},
|
|
}
|
|
for name, v := range tests {
|
|
v := v
|
|
t.Run(name, func(t *testing.T) {
|
|
b := makePallocBits(v.before)
|
|
for iter, i := range v.hits {
|
|
a, _ := b.Find(v.npages, 0)
|
|
if i != a {
|
|
t.Fatalf("find #%d picked wrong index: want %d, got %d", iter+1, i, a)
|
|
}
|
|
if i != ^uint(0) {
|
|
b.AllocRange(a, uint(v.npages))
|
|
}
|
|
}
|
|
want := makePallocBits(v.after)
|
|
checkPallocBits(t, b, want)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Ensures page freeing works.
|
|
func TestPallocBitsFree(t *testing.T) {
|
|
tests := map[string]struct {
|
|
beforeInv []BitRange
|
|
afterInv []BitRange
|
|
frees []uint
|
|
npages uintptr
|
|
}{
|
|
"SomeFree": {
|
|
npages: 1,
|
|
beforeInv: []BitRange{{0, 32}, {64, 32}, {100, 1}},
|
|
frees: []uint{32},
|
|
afterInv: []BitRange{{0, 33}, {64, 32}, {100, 1}},
|
|
},
|
|
"NoneFree1": {
|
|
npages: 1,
|
|
frees: []uint{0, 1, 2, 3, 4, 5},
|
|
afterInv: []BitRange{{0, 6}},
|
|
},
|
|
"NoneFree2": {
|
|
npages: 2,
|
|
frees: []uint{0, 2, 4, 6, 8, 10},
|
|
afterInv: []BitRange{{0, 12}},
|
|
},
|
|
"NoneFree5": {
|
|
npages: 5,
|
|
frees: []uint{0, 5, 10, 15, 20},
|
|
afterInv: []BitRange{{0, 25}},
|
|
},
|
|
"NoneFree64": {
|
|
npages: 64,
|
|
frees: []uint{0, 64, 128},
|
|
afterInv: []BitRange{{0, 192}},
|
|
},
|
|
"NoneFree65": {
|
|
npages: 65,
|
|
frees: []uint{0, 65, 130},
|
|
afterInv: []BitRange{{0, 195}},
|
|
},
|
|
}
|
|
for name, v := range tests {
|
|
v := v
|
|
t.Run(name, func(t *testing.T) {
|
|
b := makePallocBits(v.beforeInv)
|
|
invertPallocBits(b)
|
|
for _, i := range v.frees {
|
|
b.Free(i, uint(v.npages))
|
|
}
|
|
want := makePallocBits(v.afterInv)
|
|
invertPallocBits(want)
|
|
checkPallocBits(t, b, want)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFindBitRange64(t *testing.T) {
|
|
check := func(x uint64, n uint, result uint) {
|
|
i := FindBitRange64(x, n)
|
|
if result == ^uint(0) && i < 64 {
|
|
t.Errorf("case (%016x, %d): got %d, want failure", x, n, i)
|
|
} else if result != ^uint(0) && i != result {
|
|
t.Errorf("case (%016x, %d): got %d, want %d", x, n, i, result)
|
|
}
|
|
}
|
|
for i := uint(1); i <= 64; i++ {
|
|
check(^uint64(0), i, 0)
|
|
}
|
|
for i := uint(1); i <= 64; i++ {
|
|
check(0, i, ^uint(0))
|
|
}
|
|
check(0x8000000000000000, 1, 63)
|
|
check(0xc000010001010000, 2, 62)
|
|
check(0xc000010001030000, 2, 16)
|
|
check(0xe000030001030000, 3, 61)
|
|
check(0xe000030001070000, 3, 16)
|
|
check(0xffff03ff01070000, 16, 48)
|
|
check(0xffff03ff0107ffff, 16, 0)
|
|
check(0x0fff03ff01079fff, 16, ^uint(0))
|
|
}
|
|
|
|
func BenchmarkFindBitRange64(b *testing.B) {
|
|
patterns := []uint64{
|
|
0,
|
|
^uint64(0),
|
|
0xaa,
|
|
0xaaaaaaaaaaaaaaaa,
|
|
0x80000000aaaaaaaa,
|
|
0xaaaaaaaa00000001,
|
|
0xbbbbbbbbbbbbbbbb,
|
|
0x80000000bbbbbbbb,
|
|
0xbbbbbbbb00000001,
|
|
0xcccccccccccccccc,
|
|
0x4444444444444444,
|
|
0x4040404040404040,
|
|
0x4000400040004000,
|
|
}
|
|
sizes := []uint{
|
|
2, 8, 32,
|
|
}
|
|
for _, pattern := range patterns {
|
|
for _, size := range sizes {
|
|
b.Run(fmt.Sprintf("Pattern%02XSize%d", pattern, size), func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
FindBitRange64(pattern, size)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|