Source file
src/runtime/mgcscavenge_test.go
Documentation: runtime
1
2
3
4
5 package runtime_test
6
7 import (
8 "fmt"
9 "math/rand"
10 . "runtime"
11 "testing"
12 )
13
14
15
16 func makePallocData(alloc, scavenged []BitRange) *PallocData {
17 b := new(PallocData)
18 for _, v := range alloc {
19 if v.N == 0 {
20
21
22 continue
23 }
24 b.AllocRange(v.I, v.N)
25 }
26 for _, v := range scavenged {
27 if v.N == 0 {
28
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
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
85 check(rand.Uint64(), m)
86
87 if m > 1 {
88
89
90
91 val := uint64(0)
92 for n := uint(0); n < 64; n += m {
93
94
95
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
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["EndFree"+suffix] = test{
156 alloc: []BitRange{{0, PallocChunkPages - uint(m)}},
157 min: m,
158 max: PallocChunkPages,
159 want: BitRange{PallocChunkPages - uint(m), uint(m)},
160 }
161 tests["Straddle64"+suffix] = test{
162 alloc: []BitRange{{0, 64 - uint(m)}, {64 + uint(m), PallocChunkPages - (64 + uint(m))}},
163 min: m,
164 max: 2 * m,
165 want: BitRange{64 - uint(m), 2 * uint(m)},
166 }
167 tests["BottomEdge64WithFull"+suffix] = test{
168 alloc: []BitRange{{64, 64}, {128 + 3*uint(m), PallocChunkPages - (128 + 3*uint(m))}},
169 scavenged: []BitRange{{1, 10}},
170 min: m,
171 max: 3 * m,
172 want: BitRange{128, 3 * uint(m)},
173 }
174 tests["BottomEdge64WithPocket"+suffix] = test{
175 alloc: []BitRange{{64, 62}, {127, 1}, {128 + 3*uint(m), PallocChunkPages - (128 + 3*uint(m))}},
176 scavenged: []BitRange{{1, 10}},
177 min: m,
178 max: 3 * m,
179 want: BitRange{128, 3 * uint(m)},
180 }
181 tests["Max0"+suffix] = test{
182 scavenged: []BitRange{{0, PallocChunkPages - uint(m)}},
183 min: m,
184 max: 0,
185 want: BitRange{PallocChunkPages - uint(m), uint(m)},
186 }
187 if m <= 8 {
188 tests["OneFree"] = test{
189 alloc: []BitRange{{0, 40}, {40 + uint(m), PallocChunkPages - (40 + uint(m))}},
190 min: m,
191 max: PallocChunkPages,
192 want: BitRange{40, uint(m)},
193 }
194 tests["OneScavenged"] = test{
195 alloc: []BitRange{{0, 40}, {40 + uint(m), PallocChunkPages - (40 + uint(m))}},
196 scavenged: []BitRange{{40, 1}},
197 min: m,
198 max: PallocChunkPages,
199 want: BitRange{0, 0},
200 }
201 }
202 if m > 1 {
203 tests["MaxUnaligned"+suffix] = test{
204 scavenged: []BitRange{{0, PallocChunkPages - uint(m*2-1)}},
205 min: m,
206 max: m - 2,
207 want: BitRange{PallocChunkPages - uint(m), uint(m)},
208 }
209 tests["SkipSmall"+suffix] = test{
210 alloc: []BitRange{{0, 64 - uint(m)}, {64, 5}, {70, 11}, {82, PallocChunkPages - 82}},
211 min: m,
212 max: m,
213 want: BitRange{64 - uint(m), uint(m)},
214 }
215 tests["SkipMisaligned"+suffix] = test{
216 alloc: []BitRange{{0, 64 - uint(m)}, {64, 63}, {127 + uint(m), PallocChunkPages - (127 + uint(m))}},
217 min: m,
218 max: m,
219 want: BitRange{64 - uint(m), uint(m)},
220 }
221 tests["MaxLessThan"+suffix] = test{
222 scavenged: []BitRange{{0, PallocChunkPages - uint(m)}},
223 min: m,
224 max: 1,
225 want: BitRange{PallocChunkPages - uint(m), uint(m)},
226 }
227 }
228 }
229 if PhysHugePageSize > uintptr(PageSize) {
230
231 bits := uint(PhysHugePageSize / uintptr(PageSize))
232 if bits < PallocChunkPages {
233 tests["PreserveHugePageBottom"] = test{
234 alloc: []BitRange{{bits + 2, PallocChunkPages - (bits + 2)}},
235 min: 1,
236 max: 3,
237 want: BitRange{0, bits + 2},
238 }
239 if 3*bits < PallocChunkPages {
240
241 tests["PreserveHugePageMiddle"] = test{
242 alloc: []BitRange{{0, bits - 10}, {2*bits + 10, PallocChunkPages - (2*bits + 10)}},
243 min: 1,
244 max: 12,
245 want: BitRange{bits, bits + 10},
246 }
247 }
248 tests["PreserveHugePageTop"] = test{
249 alloc: []BitRange{{0, PallocChunkPages - bits}},
250 min: 1,
251 max: 1,
252 want: BitRange{PallocChunkPages - bits, bits},
253 }
254 } else if bits == PallocChunkPages {
255 tests["PreserveHugePageAll"] = test{
256 min: 1,
257 max: 1,
258 want: BitRange{0, PallocChunkPages},
259 }
260 } else {
261
262
263
264 tests["PreserveHugePageNone"] = test{
265 min: 1,
266 max: 1,
267 want: BitRange{PallocChunkPages - 1, 1},
268 }
269 }
270 }
271 for name, v := range tests {
272 v := v
273 t.Run(name, func(t *testing.T) {
274 b := makePallocData(v.alloc, v.scavenged)
275 start, size := b.FindScavengeCandidate(PallocChunkPages-1, v.min, v.max)
276 got := BitRange{start, size}
277 if !(got.N == 0 && v.want.N == 0) && got != v.want {
278 t.Fatalf("candidate mismatch: got %v, want %v", got, v.want)
279 }
280 })
281 }
282 }
283
284
285 func TestPageAllocScavenge(t *testing.T) {
286 if GOOS == "openbsd" && testing.Short() {
287 t.Skip("skipping because virtual memory is limited; see #36210")
288 }
289 type test struct {
290 request, expect uintptr
291 }
292 minPages := PhysPageSize / PageSize
293 if minPages < 1 {
294 minPages = 1
295 }
296 type setup struct {
297 beforeAlloc map[ChunkIdx][]BitRange
298 beforeScav map[ChunkIdx][]BitRange
299 expect []test
300 afterScav map[ChunkIdx][]BitRange
301 }
302 tests := map[string]setup{
303 "AllFreeUnscavExhaust": {
304 beforeAlloc: map[ChunkIdx][]BitRange{
305 BaseChunkIdx: {},
306 BaseChunkIdx + 1: {},
307 BaseChunkIdx + 2: {},
308 },
309 beforeScav: map[ChunkIdx][]BitRange{
310 BaseChunkIdx: {},
311 BaseChunkIdx + 1: {},
312 BaseChunkIdx + 2: {},
313 },
314 expect: []test{
315 {^uintptr(0), 3 * PallocChunkPages * PageSize},
316 },
317 afterScav: map[ChunkIdx][]BitRange{
318 BaseChunkIdx: {{0, PallocChunkPages}},
319 BaseChunkIdx + 1: {{0, PallocChunkPages}},
320 BaseChunkIdx + 2: {{0, PallocChunkPages}},
321 },
322 },
323 "NoneFreeUnscavExhaust": {
324 beforeAlloc: map[ChunkIdx][]BitRange{
325 BaseChunkIdx: {{0, PallocChunkPages}},
326 BaseChunkIdx + 1: {},
327 BaseChunkIdx + 2: {{0, PallocChunkPages}},
328 },
329 beforeScav: map[ChunkIdx][]BitRange{
330 BaseChunkIdx: {},
331 BaseChunkIdx + 1: {{0, PallocChunkPages}},
332 BaseChunkIdx + 2: {},
333 },
334 expect: []test{
335 {^uintptr(0), 0},
336 },
337 afterScav: map[ChunkIdx][]BitRange{
338 BaseChunkIdx: {},
339 BaseChunkIdx + 1: {{0, PallocChunkPages}},
340 BaseChunkIdx + 2: {},
341 },
342 },
343 "ScavHighestPageFirst": {
344 beforeAlloc: map[ChunkIdx][]BitRange{
345 BaseChunkIdx: {},
346 },
347 beforeScav: map[ChunkIdx][]BitRange{
348 BaseChunkIdx: {{uint(minPages), PallocChunkPages - uint(2*minPages)}},
349 },
350 expect: []test{
351 {1, minPages * PageSize},
352 },
353 afterScav: map[ChunkIdx][]BitRange{
354 BaseChunkIdx: {{uint(minPages), PallocChunkPages - uint(minPages)}},
355 },
356 },
357 "ScavMultiple": {
358 beforeAlloc: map[ChunkIdx][]BitRange{
359 BaseChunkIdx: {},
360 },
361 beforeScav: map[ChunkIdx][]BitRange{
362 BaseChunkIdx: {{uint(minPages), PallocChunkPages - uint(2*minPages)}},
363 },
364 expect: []test{
365 {minPages * PageSize, minPages * PageSize},
366 {minPages * PageSize, minPages * PageSize},
367 },
368 afterScav: map[ChunkIdx][]BitRange{
369 BaseChunkIdx: {{0, PallocChunkPages}},
370 },
371 },
372 "ScavMultiple2": {
373 beforeAlloc: map[ChunkIdx][]BitRange{
374 BaseChunkIdx: {},
375 BaseChunkIdx + 1: {},
376 },
377 beforeScav: map[ChunkIdx][]BitRange{
378 BaseChunkIdx: {{uint(minPages), PallocChunkPages - uint(2*minPages)}},
379 BaseChunkIdx + 1: {{0, PallocChunkPages - uint(2*minPages)}},
380 },
381 expect: []test{
382 {2 * minPages * PageSize, 2 * minPages * PageSize},
383 {minPages * PageSize, minPages * PageSize},
384 {minPages * PageSize, minPages * PageSize},
385 },
386 afterScav: map[ChunkIdx][]BitRange{
387 BaseChunkIdx: {{0, PallocChunkPages}},
388 BaseChunkIdx + 1: {{0, PallocChunkPages}},
389 },
390 },
391 "ScavDiscontiguous": {
392 beforeAlloc: map[ChunkIdx][]BitRange{
393 BaseChunkIdx: {},
394 BaseChunkIdx + 0xe: {},
395 },
396 beforeScav: map[ChunkIdx][]BitRange{
397 BaseChunkIdx: {{uint(minPages), PallocChunkPages - uint(2*minPages)}},
398 BaseChunkIdx + 0xe: {{uint(2 * minPages), PallocChunkPages - uint(2*minPages)}},
399 },
400 expect: []test{
401 {2 * minPages * PageSize, 2 * minPages * PageSize},
402 {^uintptr(0), 2 * minPages * PageSize},
403 {^uintptr(0), 0},
404 },
405 afterScav: map[ChunkIdx][]BitRange{
406 BaseChunkIdx: {{0, PallocChunkPages}},
407 BaseChunkIdx + 0xe: {{0, PallocChunkPages}},
408 },
409 },
410 }
411 if PageAlloc64Bit != 0 {
412 tests["ScavAllVeryDiscontiguous"] = setup{
413 beforeAlloc: map[ChunkIdx][]BitRange{
414 BaseChunkIdx: {},
415 BaseChunkIdx + 0x1000: {},
416 },
417 beforeScav: map[ChunkIdx][]BitRange{
418 BaseChunkIdx: {},
419 BaseChunkIdx + 0x1000: {},
420 },
421 expect: []test{
422 {^uintptr(0), 2 * PallocChunkPages * PageSize},
423 {^uintptr(0), 0},
424 },
425 afterScav: map[ChunkIdx][]BitRange{
426 BaseChunkIdx: {{0, PallocChunkPages}},
427 BaseChunkIdx + 0x1000: {{0, PallocChunkPages}},
428 },
429 }
430 }
431 for name, v := range tests {
432 v := v
433 runTest := func(t *testing.T, mayUnlock bool) {
434 b := NewPageAlloc(v.beforeAlloc, v.beforeScav)
435 defer FreePageAlloc(b)
436
437 for iter, h := range v.expect {
438 if got := b.Scavenge(h.request, mayUnlock); got != h.expect {
439 t.Fatalf("bad scavenge #%d: want %d, got %d", iter+1, h.expect, got)
440 }
441 }
442 want := NewPageAlloc(v.beforeAlloc, v.afterScav)
443 defer FreePageAlloc(want)
444
445 checkPageAlloc(t, want, b)
446 }
447 t.Run(name, func(t *testing.T) {
448 runTest(t, false)
449 })
450 t.Run(name+"MayUnlock", func(t *testing.T) {
451 runTest(t, true)
452 })
453 }
454 }
455
View as plain text