github.com/rafaeltorres324/go/src@v0.0.0-20210519164414-9fdf653a9838/runtime/mgcscavenge_test.go (about) 1 // Copyright 2019 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package runtime_test 6 7 import ( 8 "fmt" 9 "math/rand" 10 . "runtime" 11 "testing" 12 ) 13 14 // makePallocData produces an initialized PallocData by setting 15 // the ranges of described in alloc and scavenge. 16 func makePallocData(alloc, scavenged []BitRange) *PallocData { 17 b := new(PallocData) 18 for _, v := range alloc { 19 if v.N == 0 { 20 // Skip N==0. It's harmless and allocRange doesn't 21 // handle this case. 22 continue 23 } 24 b.AllocRange(v.I, v.N) 25 } 26 for _, v := range scavenged { 27 if v.N == 0 { 28 // See the previous loop. 29 continue 30 } 31 b.ScavengedSetRange(v.I, v.N) 32 } 33 return b 34 } 35 36 func TestFillAligned(t *testing.T) { 37 fillAlignedSlow := func(x uint64, m uint) uint64 { 38 if m == 1 { 39 return x 40 } 41 out := uint64(0) 42 for i := uint(0); i < 64; i += m { 43 for j := uint(0); j < m; j++ { 44 if x&(uint64(1)<<(i+j)) != 0 { 45 out |= ((uint64(1) << m) - 1) << i 46 break 47 } 48 } 49 } 50 return out 51 } 52 check := func(x uint64, m uint) { 53 want := fillAlignedSlow(x, m) 54 if got := FillAligned(x, m); got != want { 55 t.Logf("got: %064b", got) 56 t.Logf("want: %064b", want) 57 t.Errorf("bad fillAligned(%016x, %d)", x, m) 58 } 59 } 60 for m := uint(1); m <= 64; m *= 2 { 61 tests := []uint64{ 62 0x0000000000000000, 63 0x00000000ffffffff, 64 0xffffffff00000000, 65 0x8000000000000001, 66 0xf00000000000000f, 67 0xf00000010050000f, 68 0xffffffffffffffff, 69 0x0000000000000001, 70 0x0000000000000002, 71 0x0000000000000008, 72 uint64(1) << (m - 1), 73 uint64(1) << m, 74 // Try a few fixed arbitrary examples. 75 0xb02b9effcf137016, 76 0x3975a076a9fbff18, 77 0x0f8c88ec3b81506e, 78 0x60f14d80ef2fa0e6, 79 } 80 for _, test := range tests { 81 check(test, m) 82 } 83 for i := 0; i < 1000; i++ { 84 // Try a pseudo-random numbers. 85 check(rand.Uint64(), m) 86 87 if m > 1 { 88 // For m != 1, let's construct a slightly more interesting 89 // random test. Generate a bitmap which is either 0 or 90 // randomly set bits for each m-aligned group of m bits. 91 val := uint64(0) 92 for n := uint(0); n < 64; n += m { 93 // For each group of m bits, flip a coin: 94 // * Leave them as zero. 95 // * Set them randomly. 96 if rand.Uint64()%2 == 0 { 97 val |= (rand.Uint64() & ((1 << m) - 1)) << n 98 } 99 } 100 check(val, m) 101 } 102 } 103 } 104 } 105 106 func TestPallocDataFindScavengeCandidate(t *testing.T) { 107 type test struct { 108 alloc, scavenged []BitRange 109 min, max uintptr 110 want BitRange 111 } 112 tests := map[string]test{ 113 "MixedMin1": { 114 alloc: []BitRange{{0, 40}, {42, PallocChunkPages - 42}}, 115 scavenged: []BitRange{{0, 41}, {42, PallocChunkPages - 42}}, 116 min: 1, 117 max: PallocChunkPages, 118 want: BitRange{41, 1}, 119 }, 120 "MultiMin1": { 121 alloc: []BitRange{{0, 63}, {65, 20}, {87, PallocChunkPages - 87}}, 122 scavenged: []BitRange{{86, 1}}, 123 min: 1, 124 max: PallocChunkPages, 125 want: BitRange{85, 1}, 126 }, 127 } 128 // Try out different page minimums. 129 for m := uintptr(1); m <= 64; m *= 2 { 130 suffix := fmt.Sprintf("Min%d", m) 131 tests["AllFree"+suffix] = test{ 132 min: m, 133 max: PallocChunkPages, 134 want: BitRange{0, PallocChunkPages}, 135 } 136 tests["AllScavenged"+suffix] = test{ 137 scavenged: []BitRange{{0, PallocChunkPages}}, 138 min: m, 139 max: PallocChunkPages, 140 want: BitRange{0, 0}, 141 } 142 tests["NoneFree"+suffix] = test{ 143 alloc: []BitRange{{0, PallocChunkPages}}, 144 scavenged: []BitRange{{PallocChunkPages / 2, PallocChunkPages / 2}}, 145 min: m, 146 max: PallocChunkPages, 147 want: BitRange{0, 0}, 148 } 149 tests["StartFree"+suffix] = test{ 150 alloc: []BitRange{{uint(m), PallocChunkPages - uint(m)}}, 151 min: m, 152 max: PallocChunkPages, 153 want: BitRange{0, uint(m)}, 154 } 155 tests["StartFree"+suffix] = test{ 156 alloc: []BitRange{{uint(m), PallocChunkPages - uint(m)}}, 157 min: m, 158 max: PallocChunkPages, 159 want: BitRange{0, uint(m)}, 160 } 161 tests["EndFree"+suffix] = test{ 162 alloc: []BitRange{{0, PallocChunkPages - uint(m)}}, 163 min: m, 164 max: PallocChunkPages, 165 want: BitRange{PallocChunkPages - uint(m), uint(m)}, 166 } 167 tests["Straddle64"+suffix] = test{ 168 alloc: []BitRange{{0, 64 - uint(m)}, {64 + uint(m), PallocChunkPages - (64 + uint(m))}}, 169 min: m, 170 max: 2 * m, 171 want: BitRange{64 - uint(m), 2 * uint(m)}, 172 } 173 tests["BottomEdge64WithFull"+suffix] = test{ 174 alloc: []BitRange{{64, 64}, {128 + 3*uint(m), PallocChunkPages - (128 + 3*uint(m))}}, 175 scavenged: []BitRange{{1, 10}}, 176 min: m, 177 max: 3 * m, 178 want: BitRange{128, 3 * uint(m)}, 179 } 180 tests["BottomEdge64WithPocket"+suffix] = test{ 181 alloc: []BitRange{{64, 62}, {127, 1}, {128 + 3*uint(m), PallocChunkPages - (128 + 3*uint(m))}}, 182 scavenged: []BitRange{{1, 10}}, 183 min: m, 184 max: 3 * m, 185 want: BitRange{128, 3 * uint(m)}, 186 } 187 tests["Max0"+suffix] = test{ 188 scavenged: []BitRange{{0, PallocChunkPages - uint(m)}}, 189 min: m, 190 max: 0, 191 want: BitRange{PallocChunkPages - uint(m), uint(m)}, 192 } 193 if m <= 8 { 194 tests["OneFree"] = test{ 195 alloc: []BitRange{{0, 40}, {40 + uint(m), PallocChunkPages - (40 + uint(m))}}, 196 min: m, 197 max: PallocChunkPages, 198 want: BitRange{40, uint(m)}, 199 } 200 tests["OneScavenged"] = test{ 201 alloc: []BitRange{{0, 40}, {40 + uint(m), PallocChunkPages - (40 + uint(m))}}, 202 scavenged: []BitRange{{40, 1}}, 203 min: m, 204 max: PallocChunkPages, 205 want: BitRange{0, 0}, 206 } 207 } 208 if m > 1 { 209 tests["MaxUnaligned"+suffix] = test{ 210 scavenged: []BitRange{{0, PallocChunkPages - uint(m*2-1)}}, 211 min: m, 212 max: m - 2, 213 want: BitRange{PallocChunkPages - uint(m), uint(m)}, 214 } 215 tests["SkipSmall"+suffix] = test{ 216 alloc: []BitRange{{0, 64 - uint(m)}, {64, 5}, {70, 11}, {82, PallocChunkPages - 82}}, 217 min: m, 218 max: m, 219 want: BitRange{64 - uint(m), uint(m)}, 220 } 221 tests["SkipMisaligned"+suffix] = test{ 222 alloc: []BitRange{{0, 64 - uint(m)}, {64, 63}, {127 + uint(m), PallocChunkPages - (127 + uint(m))}}, 223 min: m, 224 max: m, 225 want: BitRange{64 - uint(m), uint(m)}, 226 } 227 tests["MaxLessThan"+suffix] = test{ 228 scavenged: []BitRange{{0, PallocChunkPages - uint(m)}}, 229 min: m, 230 max: 1, 231 want: BitRange{PallocChunkPages - uint(m), uint(m)}, 232 } 233 } 234 } 235 if PhysHugePageSize > uintptr(PageSize) { 236 // Check hugepage preserving behavior. 237 bits := uint(PhysHugePageSize / uintptr(PageSize)) 238 if bits < PallocChunkPages { 239 tests["PreserveHugePageBottom"] = test{ 240 alloc: []BitRange{{bits + 2, PallocChunkPages - (bits + 2)}}, 241 min: 1, 242 max: 3, // Make it so that max would have us try to break the huge page. 243 want: BitRange{0, bits + 2}, 244 } 245 if 3*bits < PallocChunkPages { 246 // We need at least 3 huge pages in a chunk for this test to make sense. 247 tests["PreserveHugePageMiddle"] = test{ 248 alloc: []BitRange{{0, bits - 10}, {2*bits + 10, PallocChunkPages - (2*bits + 10)}}, 249 min: 1, 250 max: 12, // Make it so that max would have us try to break the huge page. 251 want: BitRange{bits, bits + 10}, 252 } 253 } 254 tests["PreserveHugePageTop"] = test{ 255 alloc: []BitRange{{0, PallocChunkPages - bits}}, 256 min: 1, 257 max: 1, // Even one page would break a huge page in this case. 258 want: BitRange{PallocChunkPages - bits, bits}, 259 } 260 } else if bits == PallocChunkPages { 261 tests["PreserveHugePageAll"] = test{ 262 min: 1, 263 max: 1, // Even one page would break a huge page in this case. 264 want: BitRange{0, PallocChunkPages}, 265 } 266 } else { 267 // The huge page size is greater than pallocChunkPages, so it should 268 // be effectively disabled. There's no way we can possible scavenge 269 // a huge page out of this bitmap chunk. 270 tests["PreserveHugePageNone"] = test{ 271 min: 1, 272 max: 1, 273 want: BitRange{PallocChunkPages - 1, 1}, 274 } 275 } 276 } 277 for name, v := range tests { 278 v := v 279 t.Run(name, func(t *testing.T) { 280 b := makePallocData(v.alloc, v.scavenged) 281 start, size := b.FindScavengeCandidate(PallocChunkPages-1, v.min, v.max) 282 got := BitRange{start, size} 283 if !(got.N == 0 && v.want.N == 0) && got != v.want { 284 t.Fatalf("candidate mismatch: got %v, want %v", got, v.want) 285 } 286 }) 287 } 288 } 289 290 // Tests end-to-end scavenging on a pageAlloc. 291 func TestPageAllocScavenge(t *testing.T) { 292 if GOOS == "openbsd" && testing.Short() { 293 t.Skip("skipping because virtual memory is limited; see #36210") 294 } 295 type test struct { 296 request, expect uintptr 297 } 298 minPages := PhysPageSize / PageSize 299 if minPages < 1 { 300 minPages = 1 301 } 302 type setup struct { 303 beforeAlloc map[ChunkIdx][]BitRange 304 beforeScav map[ChunkIdx][]BitRange 305 expect []test 306 afterScav map[ChunkIdx][]BitRange 307 } 308 tests := map[string]setup{ 309 "AllFreeUnscavExhaust": { 310 beforeAlloc: map[ChunkIdx][]BitRange{ 311 BaseChunkIdx: {}, 312 BaseChunkIdx + 1: {}, 313 BaseChunkIdx + 2: {}, 314 }, 315 beforeScav: map[ChunkIdx][]BitRange{ 316 BaseChunkIdx: {}, 317 BaseChunkIdx + 1: {}, 318 BaseChunkIdx + 2: {}, 319 }, 320 expect: []test{ 321 {^uintptr(0), 3 * PallocChunkPages * PageSize}, 322 }, 323 afterScav: map[ChunkIdx][]BitRange{ 324 BaseChunkIdx: {{0, PallocChunkPages}}, 325 BaseChunkIdx + 1: {{0, PallocChunkPages}}, 326 BaseChunkIdx + 2: {{0, PallocChunkPages}}, 327 }, 328 }, 329 "NoneFreeUnscavExhaust": { 330 beforeAlloc: map[ChunkIdx][]BitRange{ 331 BaseChunkIdx: {{0, PallocChunkPages}}, 332 BaseChunkIdx + 1: {}, 333 BaseChunkIdx + 2: {{0, PallocChunkPages}}, 334 }, 335 beforeScav: map[ChunkIdx][]BitRange{ 336 BaseChunkIdx: {}, 337 BaseChunkIdx + 1: {{0, PallocChunkPages}}, 338 BaseChunkIdx + 2: {}, 339 }, 340 expect: []test{ 341 {^uintptr(0), 0}, 342 }, 343 afterScav: map[ChunkIdx][]BitRange{ 344 BaseChunkIdx: {}, 345 BaseChunkIdx + 1: {{0, PallocChunkPages}}, 346 BaseChunkIdx + 2: {}, 347 }, 348 }, 349 "ScavHighestPageFirst": { 350 beforeAlloc: map[ChunkIdx][]BitRange{ 351 BaseChunkIdx: {}, 352 }, 353 beforeScav: map[ChunkIdx][]BitRange{ 354 BaseChunkIdx: {{uint(minPages), PallocChunkPages - uint(2*minPages)}}, 355 }, 356 expect: []test{ 357 {1, minPages * PageSize}, 358 }, 359 afterScav: map[ChunkIdx][]BitRange{ 360 BaseChunkIdx: {{uint(minPages), PallocChunkPages - uint(minPages)}}, 361 }, 362 }, 363 "ScavMultiple": { 364 beforeAlloc: map[ChunkIdx][]BitRange{ 365 BaseChunkIdx: {}, 366 }, 367 beforeScav: map[ChunkIdx][]BitRange{ 368 BaseChunkIdx: {{uint(minPages), PallocChunkPages - uint(2*minPages)}}, 369 }, 370 expect: []test{ 371 {minPages * PageSize, minPages * PageSize}, 372 {minPages * PageSize, minPages * PageSize}, 373 }, 374 afterScav: map[ChunkIdx][]BitRange{ 375 BaseChunkIdx: {{0, PallocChunkPages}}, 376 }, 377 }, 378 "ScavMultiple2": { 379 beforeAlloc: map[ChunkIdx][]BitRange{ 380 BaseChunkIdx: {}, 381 BaseChunkIdx + 1: {}, 382 }, 383 beforeScav: map[ChunkIdx][]BitRange{ 384 BaseChunkIdx: {{uint(minPages), PallocChunkPages - uint(2*minPages)}}, 385 BaseChunkIdx + 1: {{0, PallocChunkPages - uint(2*minPages)}}, 386 }, 387 expect: []test{ 388 {2 * minPages * PageSize, 2 * minPages * PageSize}, 389 {minPages * PageSize, minPages * PageSize}, 390 {minPages * PageSize, minPages * PageSize}, 391 }, 392 afterScav: map[ChunkIdx][]BitRange{ 393 BaseChunkIdx: {{0, PallocChunkPages}}, 394 BaseChunkIdx + 1: {{0, PallocChunkPages}}, 395 }, 396 }, 397 "ScavDiscontiguous": { 398 beforeAlloc: map[ChunkIdx][]BitRange{ 399 BaseChunkIdx: {}, 400 BaseChunkIdx + 0xe: {}, 401 }, 402 beforeScav: map[ChunkIdx][]BitRange{ 403 BaseChunkIdx: {{uint(minPages), PallocChunkPages - uint(2*minPages)}}, 404 BaseChunkIdx + 0xe: {{uint(2 * minPages), PallocChunkPages - uint(2*minPages)}}, 405 }, 406 expect: []test{ 407 {2 * minPages * PageSize, 2 * minPages * PageSize}, 408 {^uintptr(0), 2 * minPages * PageSize}, 409 {^uintptr(0), 0}, 410 }, 411 afterScav: map[ChunkIdx][]BitRange{ 412 BaseChunkIdx: {{0, PallocChunkPages}}, 413 BaseChunkIdx + 0xe: {{0, PallocChunkPages}}, 414 }, 415 }, 416 } 417 if PageAlloc64Bit != 0 { 418 tests["ScavAllVeryDiscontiguous"] = setup{ 419 beforeAlloc: map[ChunkIdx][]BitRange{ 420 BaseChunkIdx: {}, 421 BaseChunkIdx + 0x1000: {}, 422 }, 423 beforeScav: map[ChunkIdx][]BitRange{ 424 BaseChunkIdx: {}, 425 BaseChunkIdx + 0x1000: {}, 426 }, 427 expect: []test{ 428 {^uintptr(0), 2 * PallocChunkPages * PageSize}, 429 {^uintptr(0), 0}, 430 }, 431 afterScav: map[ChunkIdx][]BitRange{ 432 BaseChunkIdx: {{0, PallocChunkPages}}, 433 BaseChunkIdx + 0x1000: {{0, PallocChunkPages}}, 434 }, 435 } 436 } 437 for name, v := range tests { 438 v := v 439 runTest := func(t *testing.T, mayUnlock bool) { 440 b := NewPageAlloc(v.beforeAlloc, v.beforeScav) 441 defer FreePageAlloc(b) 442 443 for iter, h := range v.expect { 444 if got := b.Scavenge(h.request, mayUnlock); got != h.expect { 445 t.Fatalf("bad scavenge #%d: want %d, got %d", iter+1, h.expect, got) 446 } 447 } 448 want := NewPageAlloc(v.beforeAlloc, v.afterScav) 449 defer FreePageAlloc(want) 450 451 checkPageAlloc(t, want, b) 452 } 453 t.Run(name, func(t *testing.T) { 454 runTest(t, false) 455 }) 456 t.Run(name+"MayUnlock", func(t *testing.T) { 457 runTest(t, true) 458 }) 459 } 460 }