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