diff options
Diffstat (limited to 'src/runtime/mpallocbits_test.go')
-rw-r--r-- | src/runtime/mpallocbits_test.go | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/src/runtime/mpallocbits_test.go b/src/runtime/mpallocbits_test.go new file mode 100644 index 0000000..5095e24 --- /dev/null +++ b/src/runtime/mpallocbits_test.go @@ -0,0 +1,551 @@ +// 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) + } + }) + } + } +} |