| // 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) | |
| } | |
| }) | |
| } | |
| } | |
| } |