Source file
src/runtime/mgcpacer_test.go
Documentation: runtime
1
2
3
4
5 package runtime_test
6
7 import (
8 "fmt"
9 "math"
10 "math/rand"
11 . "runtime"
12 "testing"
13 "time"
14 )
15
16 func TestGcPacer(t *testing.T) {
17 t.Parallel()
18
19 const initialHeapBytes = 256 << 10
20 for _, e := range []*gcExecTest{
21 {
22
23
24 name: "Steady",
25 gcPercent: 100,
26 memoryLimit: math.MaxInt64,
27 globalsBytes: 32 << 10,
28 nCores: 8,
29 allocRate: constant(33.0),
30 scanRate: constant(1024.0),
31 growthRate: constant(2.0).sum(ramp(-1.0, 12)),
32 scannableFrac: constant(1.0),
33 stackBytes: constant(8192),
34 length: 50,
35 checker: func(t *testing.T, c []gcCycleResult) {
36 n := len(c)
37 if n >= 25 {
38
39 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, GCGoalUtilization, 0.005)
40
41
42 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
43 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
44 }
45 },
46 },
47 {
48
49 name: "SteadyBigStacks",
50 gcPercent: 100,
51 memoryLimit: math.MaxInt64,
52 globalsBytes: 32 << 10,
53 nCores: 8,
54 allocRate: constant(132.0),
55 scanRate: constant(1024.0),
56 growthRate: constant(2.0).sum(ramp(-1.0, 12)),
57 scannableFrac: constant(1.0),
58 stackBytes: constant(2048).sum(ramp(128<<20, 8)),
59 length: 50,
60 checker: func(t *testing.T, c []gcCycleResult) {
61
62
63 n := len(c)
64 if n >= 25 {
65
66
67 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, GCGoalUtilization, 0.005)
68 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
69
70
71 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
72 }
73 },
74 },
75 {
76
77 name: "SteadyBigGlobals",
78 gcPercent: 100,
79 memoryLimit: math.MaxInt64,
80 globalsBytes: 128 << 20,
81 nCores: 8,
82 allocRate: constant(132.0),
83 scanRate: constant(1024.0),
84 growthRate: constant(2.0).sum(ramp(-1.0, 12)),
85 scannableFrac: constant(1.0),
86 stackBytes: constant(8192),
87 length: 50,
88 checker: func(t *testing.T, c []gcCycleResult) {
89
90
91 n := len(c)
92 if n >= 25 {
93
94
95 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, GCGoalUtilization, 0.005)
96 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
97
98
99 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
100 }
101 },
102 },
103 {
104
105 name: "StepAlloc",
106 gcPercent: 100,
107 memoryLimit: math.MaxInt64,
108 globalsBytes: 32 << 10,
109 nCores: 8,
110 allocRate: constant(33.0).sum(ramp(66.0, 1).delay(50)),
111 scanRate: constant(1024.0),
112 growthRate: constant(2.0).sum(ramp(-1.0, 12)),
113 scannableFrac: constant(1.0),
114 stackBytes: constant(8192),
115 length: 100,
116 checker: func(t *testing.T, c []gcCycleResult) {
117 n := len(c)
118 if (n >= 25 && n < 50) || n >= 75 {
119
120
121 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
122 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
123 }
124 },
125 },
126 {
127
128 name: "HeavyStepAlloc",
129 gcPercent: 100,
130 memoryLimit: math.MaxInt64,
131 globalsBytes: 32 << 10,
132 nCores: 8,
133 allocRate: constant(33).sum(ramp(330, 1).delay(50)),
134 scanRate: constant(1024.0),
135 growthRate: constant(2.0).sum(ramp(-1.0, 12)),
136 scannableFrac: constant(1.0),
137 stackBytes: constant(8192),
138 length: 100,
139 checker: func(t *testing.T, c []gcCycleResult) {
140 n := len(c)
141 if (n >= 25 && n < 50) || n >= 75 {
142
143
144 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
145 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
146 }
147 },
148 },
149 {
150
151 name: "StepScannableFrac",
152 gcPercent: 100,
153 memoryLimit: math.MaxInt64,
154 globalsBytes: 32 << 10,
155 nCores: 8,
156 allocRate: constant(128.0),
157 scanRate: constant(1024.0),
158 growthRate: constant(2.0).sum(ramp(-1.0, 12)),
159 scannableFrac: constant(0.2).sum(unit(0.5).delay(50)),
160 stackBytes: constant(8192),
161 length: 100,
162 checker: func(t *testing.T, c []gcCycleResult) {
163 n := len(c)
164 if (n >= 25 && n < 50) || n >= 75 {
165
166
167 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
168 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
169 }
170 },
171 },
172 {
173
174
175
176 name: "HighGOGC",
177 gcPercent: 1500,
178 memoryLimit: math.MaxInt64,
179 globalsBytes: 32 << 10,
180 nCores: 8,
181 allocRate: random(7, 0x53).offset(165),
182 scanRate: constant(1024.0),
183 growthRate: constant(2.0).sum(ramp(-1.0, 12), random(0.01, 0x1), unit(14).delay(25)),
184 scannableFrac: constant(1.0),
185 stackBytes: constant(8192),
186 length: 50,
187 checker: func(t *testing.T, c []gcCycleResult) {
188 n := len(c)
189 if n > 12 {
190 if n == 26 {
191
192
193
194 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.90, 15)
195 } else {
196
197
198
199
200
201
202 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.90, 1.05)
203 }
204
205
206
207
208
209 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, GCGoalUtilization, GCGoalUtilization+0.03)
210 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.03)
211 }
212 },
213 },
214 {
215
216
217 name: "OscAlloc",
218 gcPercent: 100,
219 memoryLimit: math.MaxInt64,
220 globalsBytes: 32 << 10,
221 nCores: 8,
222 allocRate: oscillate(13, 0, 8).offset(67),
223 scanRate: constant(1024.0),
224 growthRate: constant(2.0).sum(ramp(-1.0, 12)),
225 scannableFrac: constant(1.0),
226 stackBytes: constant(8192),
227 length: 50,
228 checker: func(t *testing.T, c []gcCycleResult) {
229 n := len(c)
230 if n > 12 {
231
232
233
234 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
235 assertInRange(t, "GC utilization", c[n-1].gcUtilization, 0.25, 0.3)
236 }
237 },
238 },
239 {
240
241 name: "JitterAlloc",
242 gcPercent: 100,
243 memoryLimit: math.MaxInt64,
244 globalsBytes: 32 << 10,
245 nCores: 8,
246 allocRate: random(13, 0xf).offset(132),
247 scanRate: constant(1024.0),
248 growthRate: constant(2.0).sum(ramp(-1.0, 12), random(0.01, 0xe)),
249 scannableFrac: constant(1.0),
250 stackBytes: constant(8192),
251 length: 50,
252 checker: func(t *testing.T, c []gcCycleResult) {
253 n := len(c)
254 if n > 12 {
255
256
257
258 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.025)
259 assertInRange(t, "GC utilization", c[n-1].gcUtilization, 0.25, 0.275)
260 }
261 },
262 },
263 {
264
265
266 name: "HeavyJitterAlloc",
267 gcPercent: 100,
268 memoryLimit: math.MaxInt64,
269 globalsBytes: 32 << 10,
270 nCores: 8,
271 allocRate: random(33.0, 0x0).offset(330),
272 scanRate: constant(1024.0),
273 growthRate: constant(2.0).sum(ramp(-1.0, 12), random(0.01, 0x152)),
274 scannableFrac: constant(1.0),
275 stackBytes: constant(8192),
276 length: 50,
277 checker: func(t *testing.T, c []gcCycleResult) {
278 n := len(c)
279 if n > 13 {
280
281
282
283
284 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
285
286
287 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.05)
288 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[11].gcUtilization, 0.05)
289 }
290 },
291 },
292 {
293
294
295 name: "SmallHeapSlowAlloc",
296 gcPercent: 100,
297 memoryLimit: math.MaxInt64,
298 globalsBytes: 32 << 10,
299 nCores: 8,
300 allocRate: constant(1.0),
301 scanRate: constant(2048.0),
302 growthRate: constant(2.0).sum(ramp(-1.0, 3)),
303 scannableFrac: constant(0.01),
304 stackBytes: constant(8192),
305 length: 50,
306 checker: func(t *testing.T, c []gcCycleResult) {
307 n := len(c)
308 if n > 4 {
309
310
311
312 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.925, 1.025)
313
314
315
316 assertInRange(t, "trigger ratio", c[n-1].triggerRatio(), 0.925, 0.975)
317 }
318 if n > 25 {
319
320
321
322 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, GCGoalUtilization, 0.005)
323
324 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.05)
325 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[11].gcUtilization, 0.05)
326 }
327 },
328 },
329 {
330
331
332 name: "MediumHeapSlowAlloc",
333 gcPercent: 100,
334 memoryLimit: math.MaxInt64,
335 globalsBytes: 32 << 10,
336 nCores: 8,
337 allocRate: constant(1.0),
338 scanRate: constant(2048.0),
339 growthRate: constant(2.0).sum(ramp(-1.0, 8)),
340 scannableFrac: constant(0.01),
341 stackBytes: constant(8192),
342 length: 50,
343 checker: func(t *testing.T, c []gcCycleResult) {
344 n := len(c)
345 if n > 9 {
346
347
348
349 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.925, 1.025)
350
351
352
353 assertInRange(t, "trigger ratio", c[n-1].triggerRatio(), 0.925, 0.975)
354 }
355 if n > 25 {
356
357
358
359 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, GCGoalUtilization, 0.005)
360
361 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.05)
362 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[11].gcUtilization, 0.05)
363 }
364 },
365 },
366 {
367
368
369 name: "LargeHeapSlowAlloc",
370 gcPercent: 100,
371 memoryLimit: math.MaxInt64,
372 globalsBytes: 32 << 10,
373 nCores: 8,
374 allocRate: constant(1.0),
375 scanRate: constant(2048.0),
376 growthRate: constant(4.0).sum(ramp(-3.0, 12)),
377 scannableFrac: constant(0.01),
378 stackBytes: constant(8192),
379 length: 50,
380 checker: func(t *testing.T, c []gcCycleResult) {
381 n := len(c)
382 if n > 13 {
383
384
385 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
386
387
388 assertInRange(t, "runway", c[n-1].runway(), DefaultHeapMinimum-64<<10, DefaultHeapMinimum+64<<10)
389 }
390 if n > 25 {
391
392
393
394 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, GCGoalUtilization, 0.005)
395
396 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.05)
397 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[11].gcUtilization, 0.05)
398 }
399 },
400 },
401 {
402
403
404
405
406
407 name: "SteadyMemoryLimit",
408 gcPercent: 100,
409 memoryLimit: 512 << 20,
410 globalsBytes: 32 << 10,
411 nCores: 8,
412 allocRate: constant(33.0),
413 scanRate: constant(1024.0),
414 growthRate: constant(2.0).sum(ramp(-1.0, 12)),
415 scannableFrac: constant(1.0),
416 stackBytes: constant(8192),
417 length: 50,
418 checker: func(t *testing.T, c []gcCycleResult) {
419 n := len(c)
420 if peak := c[n-1].heapPeak; peak >= applyMemoryLimitHeapGoalHeadroom(512<<20) {
421 t.Errorf("peak heap size reaches heap limit: %d", peak)
422 }
423 if n >= 25 {
424
425 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, GCGoalUtilization, 0.005)
426
427
428 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
429 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
430 }
431 },
432 },
433 {
434
435
436 name: "SteadyMemoryLimitNoGCPercent",
437 gcPercent: -1,
438 memoryLimit: 512 << 20,
439 globalsBytes: 32 << 10,
440 nCores: 8,
441 allocRate: constant(33.0),
442 scanRate: constant(1024.0),
443 growthRate: constant(2.0).sum(ramp(-1.0, 12)),
444 scannableFrac: constant(1.0),
445 stackBytes: constant(8192),
446 length: 50,
447 checker: func(t *testing.T, c []gcCycleResult) {
448 n := len(c)
449 if goal := c[n-1].heapGoal; goal != applyMemoryLimitHeapGoalHeadroom(512<<20) {
450 t.Errorf("heap goal is not the heap limit: %d", goal)
451 }
452 if n >= 25 {
453
454 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, GCGoalUtilization, 0.005)
455
456
457 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
458 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
459 }
460 },
461 },
462 {
463
464
465 name: "ExceedMemoryLimit",
466 gcPercent: 100,
467 memoryLimit: 512 << 20,
468 globalsBytes: 32 << 10,
469 nCores: 8,
470 allocRate: constant(33.0),
471 scanRate: constant(1024.0),
472 growthRate: constant(3.5).sum(ramp(-2.5, 12)),
473 scannableFrac: constant(1.0),
474 stackBytes: constant(8192),
475 length: 50,
476 checker: func(t *testing.T, c []gcCycleResult) {
477 n := len(c)
478 if n > 12 {
479
480
481 if goal, live := c[n-1].heapGoal, c[n-1].heapLive; goal != live {
482 t.Errorf("heap goal is not equal to live heap: %d != %d", goal, live)
483 }
484 }
485 if n >= 25 {
486
487
488
489 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, 1.0, 0.005)
490
491
492
493 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
494 }
495 },
496 },
497 {
498
499 name: "ExceedMemoryLimitNoGCPercent",
500 gcPercent: -1,
501 memoryLimit: 512 << 20,
502 globalsBytes: 32 << 10,
503 nCores: 8,
504 allocRate: constant(33.0),
505 scanRate: constant(1024.0),
506 growthRate: constant(3.5).sum(ramp(-2.5, 12)),
507 scannableFrac: constant(1.0),
508 stackBytes: constant(8192),
509 length: 50,
510 checker: func(t *testing.T, c []gcCycleResult) {
511 n := len(c)
512 if n < 10 {
513 if goal := c[n-1].heapGoal; goal != applyMemoryLimitHeapGoalHeadroom(512<<20) {
514 t.Errorf("heap goal is not the heap limit: %d", goal)
515 }
516 }
517 if n > 12 {
518
519
520 if goal, live := c[n-1].heapGoal, c[n-1].heapLive; goal != live {
521 t.Errorf("heap goal is not equal to live heap: %d != %d", goal, live)
522 }
523 }
524 if n >= 25 {
525
526
527
528 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, 1.0, 0.005)
529
530
531
532 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
533 }
534 },
535 },
536 {
537
538 name: "MaintainMemoryLimit",
539 gcPercent: 100,
540 memoryLimit: 512 << 20,
541 globalsBytes: 32 << 10,
542 nCores: 8,
543 allocRate: constant(33.0),
544 scanRate: constant(1024.0),
545 growthRate: constant(3.0).sum(ramp(-2.0, 12)),
546 scannableFrac: constant(1.0),
547 stackBytes: constant(8192),
548 length: 50,
549 checker: func(t *testing.T, c []gcCycleResult) {
550 n := len(c)
551 if n > 12 {
552
553 if goal := c[n-1].heapGoal; goal != applyMemoryLimitHeapGoalHeadroom(512<<20) {
554 t.Errorf("heap goal is not the heap limit: %d", goal)
555 }
556 }
557 if n >= 25 {
558
559
560 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, GCGoalUtilization, 0.005)
561
562
563
564 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
565 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
566 }
567 },
568 },
569 {
570
571 name: "MaintainMemoryLimitNoGCPercent",
572 gcPercent: -1,
573 memoryLimit: 512 << 20,
574 globalsBytes: 32 << 10,
575 nCores: 8,
576 allocRate: constant(33.0),
577 scanRate: constant(1024.0),
578 growthRate: constant(3.0).sum(ramp(-2.0, 12)),
579 scannableFrac: constant(1.0),
580 stackBytes: constant(8192),
581 length: 50,
582 checker: func(t *testing.T, c []gcCycleResult) {
583 n := len(c)
584 if goal := c[n-1].heapGoal; goal != applyMemoryLimitHeapGoalHeadroom(512<<20) {
585 t.Errorf("heap goal is not the heap limit: %d", goal)
586 }
587 if n >= 25 {
588
589
590 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, GCGoalUtilization, 0.005)
591
592
593
594 assertInEpsilon(t, "GC utilization", c[n-1].gcUtilization, c[n-2].gcUtilization, 0.005)
595 assertInRange(t, "goal ratio", c[n-1].goalRatio(), 0.95, 1.05)
596 }
597 },
598 },
599
600
601
602
603
604
605 } {
606 e := e
607 t.Run(e.name, func(t *testing.T) {
608 t.Parallel()
609
610 c := NewGCController(e.gcPercent, e.memoryLimit)
611 var bytesAllocatedBlackLast int64
612 results := make([]gcCycleResult, 0, e.length)
613 for i := 0; i < e.length; i++ {
614 cycle := e.next()
615 c.StartCycle(cycle.stackBytes, e.globalsBytes, cycle.scannableFrac, e.nCores)
616
617
618 const (
619 revisePeriod = 500 * time.Microsecond
620 rateConv = 1024 * float64(revisePeriod) / float64(time.Millisecond)
621 )
622 var nextHeapMarked int64
623 if i == 0 {
624 nextHeapMarked = initialHeapBytes
625 } else {
626 nextHeapMarked = int64(float64(int64(c.HeapMarked())-bytesAllocatedBlackLast) * cycle.growthRate)
627 }
628 globalsScanWorkLeft := int64(e.globalsBytes)
629 stackScanWorkLeft := int64(cycle.stackBytes)
630 heapScanWorkLeft := int64(float64(nextHeapMarked) * cycle.scannableFrac)
631 doWork := func(work int64) (int64, int64, int64) {
632 var deltas [3]int64
633
634
635 for i, workLeft := range []*int64{&globalsScanWorkLeft, &stackScanWorkLeft, &heapScanWorkLeft} {
636 if *workLeft == 0 {
637 continue
638 }
639 if *workLeft > work {
640 deltas[i] += work
641 *workLeft -= work
642 work = 0
643 break
644 } else {
645 deltas[i] += *workLeft
646 work -= *workLeft
647 *workLeft = 0
648 }
649 }
650 return deltas[0], deltas[1], deltas[2]
651 }
652 var (
653 gcDuration int64
654 assistTime int64
655 bytesAllocatedBlack int64
656 )
657 for heapScanWorkLeft+stackScanWorkLeft+globalsScanWorkLeft > 0 {
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696 assistRatio := c.AssistWorkPerByte()
697 utilization := assistRatio * cycle.allocRate / (assistRatio*cycle.allocRate + cycle.scanRate)
698 if utilization < GCBackgroundUtilization {
699 utilization = GCBackgroundUtilization
700 }
701
702
703 bytesScanned := int64(cycle.scanRate * rateConv * float64(e.nCores) * utilization)
704 bytesAllocated := int64(cycle.allocRate * rateConv * float64(e.nCores) * (1 - utilization))
705
706
707 globalsScanned, stackScanned, heapScanned := doWork(bytesScanned)
708
709
710
711
712 actualElapsed := revisePeriod
713 actualAllocated := bytesAllocated
714 if actualScanned := globalsScanned + stackScanned + heapScanned; actualScanned < bytesScanned {
715
716
717 actualElapsed = time.Duration(float64(actualScanned) * float64(revisePeriod) / (cycle.scanRate * rateConv * float64(e.nCores) * utilization))
718 actualAllocated = int64(cycle.allocRate * rateConv * float64(actualElapsed) / float64(revisePeriod) * float64(e.nCores) * (1 - utilization))
719 }
720
721
722 c.Revise(GCControllerReviseDelta{
723 HeapLive: actualAllocated,
724 HeapScan: int64(float64(actualAllocated) * cycle.scannableFrac),
725 HeapScanWork: heapScanned,
726 StackScanWork: stackScanned,
727 GlobalsScanWork: globalsScanned,
728 })
729
730
731 assistTime += int64(float64(actualElapsed) * float64(e.nCores) * (utilization - GCBackgroundUtilization))
732 gcDuration += int64(actualElapsed)
733 bytesAllocatedBlack += actualAllocated
734 }
735
736
737 result := gcCycleResult{
738 cycle: i + 1,
739 heapLive: c.HeapMarked(),
740 heapScannable: int64(float64(int64(c.HeapMarked())-bytesAllocatedBlackLast) * cycle.scannableFrac),
741 heapTrigger: c.Triggered(),
742 heapPeak: c.HeapLive(),
743 heapGoal: c.HeapGoal(),
744 gcUtilization: float64(assistTime)/(float64(gcDuration)*float64(e.nCores)) + GCBackgroundUtilization,
745 }
746 t.Log("GC", result.String())
747 results = append(results, result)
748
749
750 e.check(t, results)
751
752 c.EndCycle(uint64(nextHeapMarked+bytesAllocatedBlack), assistTime, gcDuration, e.nCores)
753
754 bytesAllocatedBlackLast = bytesAllocatedBlack
755 }
756 })
757 }
758 }
759
760 type gcExecTest struct {
761 name string
762
763 gcPercent int
764 memoryLimit int64
765 globalsBytes uint64
766 nCores int
767
768 allocRate float64Stream
769 scanRate float64Stream
770 growthRate float64Stream
771 scannableFrac float64Stream
772 stackBytes float64Stream
773 length int
774
775 checker func(*testing.T, []gcCycleResult)
776 }
777
778
779
780 const minRate = 0.0001
781
782 func (e *gcExecTest) next() gcCycle {
783 return gcCycle{
784 allocRate: e.allocRate.min(minRate)(),
785 scanRate: e.scanRate.min(minRate)(),
786 growthRate: e.growthRate.min(minRate)(),
787 scannableFrac: e.scannableFrac.limit(0, 1)(),
788 stackBytes: uint64(e.stackBytes.quantize(2048).min(0)()),
789 }
790 }
791
792 func (e *gcExecTest) check(t *testing.T, results []gcCycleResult) {
793 t.Helper()
794
795
796 n := len(results)
797 switch n {
798 case 0:
799 t.Fatal("no results passed to check")
800 return
801 case 1:
802 if results[0].cycle != 1 {
803 t.Error("first cycle has incorrect number")
804 }
805 default:
806 if results[n-1].cycle != results[n-2].cycle+1 {
807 t.Error("cycle numbers out of order")
808 }
809 }
810 if u := results[n-1].gcUtilization; u < 0 || u > 1 {
811 t.Fatal("GC utilization not within acceptable bounds")
812 }
813 if s := results[n-1].heapScannable; s < 0 {
814 t.Fatal("heapScannable is negative")
815 }
816 if e.checker == nil {
817 t.Fatal("test-specific checker is missing")
818 }
819
820
821 e.checker(t, results)
822 }
823
824 type gcCycle struct {
825 allocRate float64
826 scanRate float64
827 growthRate float64
828 scannableFrac float64
829 stackBytes uint64
830 }
831
832 type gcCycleResult struct {
833 cycle int
834
835
836 heapLive uint64
837 heapTrigger uint64
838 heapGoal uint64
839 heapPeak uint64
840
841
842
843
844 heapScannable int64
845 gcUtilization float64
846 }
847
848 func (r *gcCycleResult) goalRatio() float64 {
849 return float64(r.heapPeak) / float64(r.heapGoal)
850 }
851
852 func (r *gcCycleResult) runway() float64 {
853 return float64(r.heapGoal - r.heapTrigger)
854 }
855
856 func (r *gcCycleResult) triggerRatio() float64 {
857 return float64(r.heapTrigger-r.heapLive) / float64(r.heapGoal-r.heapLive)
858 }
859
860 func (r *gcCycleResult) String() string {
861 return fmt.Sprintf("%d %2.1f%% %d->%d->%d (goal: %d)", r.cycle, r.gcUtilization*100, r.heapLive, r.heapTrigger, r.heapPeak, r.heapGoal)
862 }
863
864 func assertInEpsilon(t *testing.T, name string, a, b, epsilon float64) {
865 t.Helper()
866 assertInRange(t, name, a, b-epsilon, b+epsilon)
867 }
868
869 func assertInRange(t *testing.T, name string, a, min, max float64) {
870 t.Helper()
871 if a < min || a > max {
872 t.Errorf("%s not in range (%f, %f): %f", name, min, max, a)
873 }
874 }
875
876
877
878 type float64Stream func() float64
879
880
881 func constant(c float64) float64Stream {
882 return func() float64 {
883 return c
884 }
885 }
886
887
888
889
890
891 func unit(amp float64) float64Stream {
892 dropped := false
893 return func() float64 {
894 if dropped {
895 return 0
896 }
897 dropped = true
898 return amp
899 }
900 }
901
902
903
904 func oscillate(amp, phase float64, period int) float64Stream {
905 var cycle int
906 return func() float64 {
907 p := float64(cycle)/float64(period)*2*math.Pi + phase
908 cycle++
909 if cycle == period {
910 cycle = 0
911 }
912 return math.Sin(p) * amp
913 }
914 }
915
916
917
918 func ramp(height float64, length int) float64Stream {
919 var cycle int
920 return func() float64 {
921 h := height * float64(cycle) / float64(length)
922 if cycle < length {
923 cycle++
924 }
925 return h
926 }
927 }
928
929
930
931 func random(amp float64, seed int64) float64Stream {
932 r := rand.New(rand.NewSource(seed))
933 return func() float64 {
934 return ((r.Float64() - 0.5) * 2) * amp
935 }
936 }
937
938
939
940 func (f float64Stream) delay(cycles int) float64Stream {
941 zeroes := 0
942 return func() float64 {
943 if zeroes < cycles {
944 zeroes++
945 return 0
946 }
947 return f()
948 }
949 }
950
951
952
953 func (f float64Stream) scale(amt float64) float64Stream {
954 return func() float64 {
955 return f() * amt
956 }
957 }
958
959
960
961 func (f float64Stream) offset(amt float64) float64Stream {
962 return func() float64 {
963 old := f()
964 return old + amt
965 }
966 }
967
968
969
970 func (f float64Stream) sum(fs ...float64Stream) float64Stream {
971 return func() float64 {
972 sum := f()
973 for _, s := range fs {
974 sum += s()
975 }
976 return sum
977 }
978 }
979
980
981
982 func (f float64Stream) quantize(mult float64) float64Stream {
983 return func() float64 {
984 r := f() / mult
985 if r < 0 {
986 return math.Ceil(r) * mult
987 }
988 return math.Floor(r) * mult
989 }
990 }
991
992
993
994 func (f float64Stream) min(min float64) float64Stream {
995 return func() float64 {
996 return math.Max(min, f())
997 }
998 }
999
1000
1001
1002 func (f float64Stream) max(max float64) float64Stream {
1003 return func() float64 {
1004 return math.Min(max, f())
1005 }
1006 }
1007
1008
1009
1010 func (f float64Stream) limit(min, max float64) float64Stream {
1011 return func() float64 {
1012 v := f()
1013 if v < min {
1014 v = min
1015 } else if v > max {
1016 v = max
1017 }
1018 return v
1019 }
1020 }
1021
1022 func applyMemoryLimitHeapGoalHeadroom(goal uint64) uint64 {
1023 headroom := goal / 100 * MemoryLimitHeapGoalHeadroomPercent
1024 if headroom < MemoryLimitMinHeapGoalHeadroom {
1025 headroom = MemoryLimitMinHeapGoalHeadroom
1026 }
1027 if goal < headroom || goal-headroom < headroom {
1028 goal = headroom
1029 } else {
1030 goal -= headroom
1031 }
1032 return goal
1033 }
1034
1035 func TestIdleMarkWorkerCount(t *testing.T) {
1036 const workers = 10
1037 c := NewGCController(100, math.MaxInt64)
1038 c.SetMaxIdleMarkWorkers(workers)
1039 for i := 0; i < workers; i++ {
1040 if !c.NeedIdleMarkWorker() {
1041 t.Fatalf("expected to need idle mark workers: i=%d", i)
1042 }
1043 if !c.AddIdleMarkWorker() {
1044 t.Fatalf("expected to be able to add an idle mark worker: i=%d", i)
1045 }
1046 }
1047 if c.NeedIdleMarkWorker() {
1048 t.Fatalf("expected to not need idle mark workers")
1049 }
1050 if c.AddIdleMarkWorker() {
1051 t.Fatalf("expected to not be able to add an idle mark worker")
1052 }
1053 for i := 0; i < workers; i++ {
1054 c.RemoveIdleMarkWorker()
1055 if !c.NeedIdleMarkWorker() {
1056 t.Fatalf("expected to need idle mark workers after removal: i=%d", i)
1057 }
1058 }
1059 for i := 0; i < workers-1; i++ {
1060 if !c.AddIdleMarkWorker() {
1061 t.Fatalf("expected to be able to add idle mark workers after adding again: i=%d", i)
1062 }
1063 }
1064 for i := 0; i < 10; i++ {
1065 if !c.AddIdleMarkWorker() {
1066 t.Fatalf("expected to be able to add idle mark workers interleaved: i=%d", i)
1067 }
1068 if c.AddIdleMarkWorker() {
1069 t.Fatalf("expected to not be able to add idle mark workers interleaved: i=%d", i)
1070 }
1071 c.RemoveIdleMarkWorker()
1072 }
1073
1074 c.SetMaxIdleMarkWorkers(0)
1075 if c.NeedIdleMarkWorker() {
1076 t.Fatalf("expected to not need idle mark workers after capacity set to 0")
1077 }
1078 if c.AddIdleMarkWorker() {
1079 t.Fatalf("expected to not be able to add idle mark workers after capacity set to 0")
1080 }
1081 for i := 0; i < workers-1; i++ {
1082 c.RemoveIdleMarkWorker()
1083 }
1084 if c.NeedIdleMarkWorker() {
1085 t.Fatalf("expected to not need idle mark workers after capacity set to 0")
1086 }
1087 if c.AddIdleMarkWorker() {
1088 t.Fatalf("expected to not be able to add idle mark workers after capacity set to 0")
1089 }
1090 c.SetMaxIdleMarkWorkers(1)
1091 if !c.NeedIdleMarkWorker() {
1092 t.Fatalf("expected to need idle mark workers after capacity set to 1")
1093 }
1094 if !c.AddIdleMarkWorker() {
1095 t.Fatalf("expected to be able to add idle mark workers after capacity set to 1")
1096 }
1097 }
1098
View as plain text