Source file
src/runtime/mgcpacer_test.go
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 t.Run(e.name, func(t *testing.T) {
607 t.Parallel()
608
609 c := NewGCController(e.gcPercent, e.memoryLimit)
610 var bytesAllocatedBlackLast int64
611 results := make([]gcCycleResult, 0, e.length)
612 for i := 0; i < e.length; i++ {
613 cycle := e.next()
614 c.StartCycle(cycle.stackBytes, e.globalsBytes, cycle.scannableFrac, e.nCores)
615
616
617 const (
618 revisePeriod = 500 * time.Microsecond
619 rateConv = 1024 * float64(revisePeriod) / float64(time.Millisecond)
620 )
621 var nextHeapMarked int64
622 if i == 0 {
623 nextHeapMarked = initialHeapBytes
624 } else {
625 nextHeapMarked = int64(float64(int64(c.HeapMarked())-bytesAllocatedBlackLast) * cycle.growthRate)
626 }
627 globalsScanWorkLeft := int64(e.globalsBytes)
628 stackScanWorkLeft := int64(cycle.stackBytes)
629 heapScanWorkLeft := int64(float64(nextHeapMarked) * cycle.scannableFrac)
630 doWork := func(work int64) (int64, int64, int64) {
631 var deltas [3]int64
632
633
634 for i, workLeft := range []*int64{&globalsScanWorkLeft, &stackScanWorkLeft, &heapScanWorkLeft} {
635 if *workLeft == 0 {
636 continue
637 }
638 if *workLeft > work {
639 deltas[i] += work
640 *workLeft -= work
641 work = 0
642 break
643 } else {
644 deltas[i] += *workLeft
645 work -= *workLeft
646 *workLeft = 0
647 }
648 }
649 return deltas[0], deltas[1], deltas[2]
650 }
651 var (
652 gcDuration int64
653 assistTime int64
654 bytesAllocatedBlack int64
655 )
656 for heapScanWorkLeft+stackScanWorkLeft+globalsScanWorkLeft > 0 {
657
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 assistRatio := c.AssistWorkPerByte()
696 utilization := assistRatio * cycle.allocRate / (assistRatio*cycle.allocRate + cycle.scanRate)
697 if utilization < GCBackgroundUtilization {
698 utilization = GCBackgroundUtilization
699 }
700
701
702 bytesScanned := int64(cycle.scanRate * rateConv * float64(e.nCores) * utilization)
703 bytesAllocated := int64(cycle.allocRate * rateConv * float64(e.nCores) * (1 - utilization))
704
705
706 globalsScanned, stackScanned, heapScanned := doWork(bytesScanned)
707
708
709
710
711 actualElapsed := revisePeriod
712 actualAllocated := bytesAllocated
713 if actualScanned := globalsScanned + stackScanned + heapScanned; actualScanned < bytesScanned {
714
715
716 actualElapsed = time.Duration(float64(actualScanned) * float64(revisePeriod) / (cycle.scanRate * rateConv * float64(e.nCores) * utilization))
717 actualAllocated = int64(cycle.allocRate * rateConv * float64(actualElapsed) / float64(revisePeriod) * float64(e.nCores) * (1 - utilization))
718 }
719
720
721 c.Revise(GCControllerReviseDelta{
722 HeapLive: actualAllocated,
723 HeapScan: int64(float64(actualAllocated) * cycle.scannableFrac),
724 HeapScanWork: heapScanned,
725 StackScanWork: stackScanned,
726 GlobalsScanWork: globalsScanned,
727 })
728
729
730 assistTime += int64(float64(actualElapsed) * float64(e.nCores) * (utilization - GCBackgroundUtilization))
731 gcDuration += int64(actualElapsed)
732 bytesAllocatedBlack += actualAllocated
733 }
734
735
736 result := gcCycleResult{
737 cycle: i + 1,
738 heapLive: c.HeapMarked(),
739 heapScannable: int64(float64(int64(c.HeapMarked())-bytesAllocatedBlackLast) * cycle.scannableFrac),
740 heapTrigger: c.Triggered(),
741 heapPeak: c.HeapLive(),
742 heapGoal: c.HeapGoal(),
743 gcUtilization: float64(assistTime)/(float64(gcDuration)*float64(e.nCores)) + GCBackgroundUtilization,
744 }
745 t.Log("GC", result.String())
746 results = append(results, result)
747
748
749 e.check(t, results)
750
751 c.EndCycle(uint64(nextHeapMarked+bytesAllocatedBlack), assistTime, gcDuration, e.nCores)
752
753 bytesAllocatedBlackLast = bytesAllocatedBlack
754 }
755 })
756 }
757 }
758
759 type gcExecTest struct {
760 name string
761
762 gcPercent int
763 memoryLimit int64
764 globalsBytes uint64
765 nCores int
766
767 allocRate float64Stream
768 scanRate float64Stream
769 growthRate float64Stream
770 scannableFrac float64Stream
771 stackBytes float64Stream
772 length int
773
774 checker func(*testing.T, []gcCycleResult)
775 }
776
777
778
779 const minRate = 0.0001
780
781 func (e *gcExecTest) next() gcCycle {
782 return gcCycle{
783 allocRate: e.allocRate.min(minRate)(),
784 scanRate: e.scanRate.min(minRate)(),
785 growthRate: e.growthRate.min(minRate)(),
786 scannableFrac: e.scannableFrac.limit(0, 1)(),
787 stackBytes: uint64(e.stackBytes.quantize(2048).min(0)()),
788 }
789 }
790
791 func (e *gcExecTest) check(t *testing.T, results []gcCycleResult) {
792 t.Helper()
793
794
795 n := len(results)
796 switch n {
797 case 0:
798 t.Fatal("no results passed to check")
799 return
800 case 1:
801 if results[0].cycle != 1 {
802 t.Error("first cycle has incorrect number")
803 }
804 default:
805 if results[n-1].cycle != results[n-2].cycle+1 {
806 t.Error("cycle numbers out of order")
807 }
808 }
809 if u := results[n-1].gcUtilization; u < 0 || u > 1 {
810 t.Fatal("GC utilization not within acceptable bounds")
811 }
812 if s := results[n-1].heapScannable; s < 0 {
813 t.Fatal("heapScannable is negative")
814 }
815 if e.checker == nil {
816 t.Fatal("test-specific checker is missing")
817 }
818
819
820 e.checker(t, results)
821 }
822
823 type gcCycle struct {
824 allocRate float64
825 scanRate float64
826 growthRate float64
827 scannableFrac float64
828 stackBytes uint64
829 }
830
831 type gcCycleResult struct {
832 cycle int
833
834
835 heapLive uint64
836 heapTrigger uint64
837 heapGoal uint64
838 heapPeak uint64
839
840
841
842
843 heapScannable int64
844 gcUtilization float64
845 }
846
847 func (r *gcCycleResult) goalRatio() float64 {
848 return float64(r.heapPeak) / float64(r.heapGoal)
849 }
850
851 func (r *gcCycleResult) runway() float64 {
852 return float64(r.heapGoal - r.heapTrigger)
853 }
854
855 func (r *gcCycleResult) triggerRatio() float64 {
856 return float64(r.heapTrigger-r.heapLive) / float64(r.heapGoal-r.heapLive)
857 }
858
859 func (r *gcCycleResult) String() string {
860 return fmt.Sprintf("%d %2.1f%% %d->%d->%d (goal: %d)", r.cycle, r.gcUtilization*100, r.heapLive, r.heapTrigger, r.heapPeak, r.heapGoal)
861 }
862
863 func assertInEpsilon(t *testing.T, name string, a, b, epsilon float64) {
864 t.Helper()
865 assertInRange(t, name, a, b-epsilon, b+epsilon)
866 }
867
868 func assertInRange(t *testing.T, name string, a, min, max float64) {
869 t.Helper()
870 if a < min || a > max {
871 t.Errorf("%s not in range (%f, %f): %f", name, min, max, a)
872 }
873 }
874
875
876
877 type float64Stream func() float64
878
879
880 func constant(c float64) float64Stream {
881 return func() float64 {
882 return c
883 }
884 }
885
886
887
888
889
890 func unit(amp float64) float64Stream {
891 dropped := false
892 return func() float64 {
893 if dropped {
894 return 0
895 }
896 dropped = true
897 return amp
898 }
899 }
900
901
902
903 func oscillate(amp, phase float64, period int) float64Stream {
904 var cycle int
905 return func() float64 {
906 p := float64(cycle)/float64(period)*2*math.Pi + phase
907 cycle++
908 if cycle == period {
909 cycle = 0
910 }
911 return math.Sin(p) * amp
912 }
913 }
914
915
916
917 func ramp(height float64, length int) float64Stream {
918 var cycle int
919 return func() float64 {
920 h := height * float64(cycle) / float64(length)
921 if cycle < length {
922 cycle++
923 }
924 return h
925 }
926 }
927
928
929
930 func random(amp float64, seed int64) float64Stream {
931 r := rand.New(rand.NewSource(seed))
932 return func() float64 {
933 return ((r.Float64() - 0.5) * 2) * amp
934 }
935 }
936
937
938
939 func (f float64Stream) delay(cycles int) float64Stream {
940 zeroes := 0
941 return func() float64 {
942 if zeroes < cycles {
943 zeroes++
944 return 0
945 }
946 return f()
947 }
948 }
949
950
951
952 func (f float64Stream) scale(amt float64) float64Stream {
953 return func() float64 {
954 return f() * amt
955 }
956 }
957
958
959
960 func (f float64Stream) offset(amt float64) float64Stream {
961 return func() float64 {
962 old := f()
963 return old + amt
964 }
965 }
966
967
968
969 func (f float64Stream) sum(fs ...float64Stream) float64Stream {
970 return func() float64 {
971 sum := f()
972 for _, s := range fs {
973 sum += s()
974 }
975 return sum
976 }
977 }
978
979
980
981 func (f float64Stream) quantize(mult float64) float64Stream {
982 return func() float64 {
983 r := f() / mult
984 if r < 0 {
985 return math.Ceil(r) * mult
986 }
987 return math.Floor(r) * mult
988 }
989 }
990
991
992
993 func (f float64Stream) min(min float64) float64Stream {
994 return func() float64 {
995 return math.Max(min, f())
996 }
997 }
998
999
1000
1001 func (f float64Stream) max(max float64) float64Stream {
1002 return func() float64 {
1003 return math.Min(max, f())
1004 }
1005 }
1006
1007
1008
1009 func (f float64Stream) limit(min, max float64) float64Stream {
1010 return func() float64 {
1011 v := f()
1012 if v < min {
1013 v = min
1014 } else if v > max {
1015 v = max
1016 }
1017 return v
1018 }
1019 }
1020
1021 func applyMemoryLimitHeapGoalHeadroom(goal uint64) uint64 {
1022 headroom := goal / 100 * MemoryLimitHeapGoalHeadroomPercent
1023 if headroom < MemoryLimitMinHeapGoalHeadroom {
1024 headroom = MemoryLimitMinHeapGoalHeadroom
1025 }
1026 if goal < headroom || goal-headroom < headroom {
1027 goal = headroom
1028 } else {
1029 goal -= headroom
1030 }
1031 return goal
1032 }
1033
1034 func TestIdleMarkWorkerCount(t *testing.T) {
1035 const workers = 10
1036 c := NewGCController(100, math.MaxInt64)
1037 c.SetMaxIdleMarkWorkers(workers)
1038 for i := 0; i < workers; i++ {
1039 if !c.NeedIdleMarkWorker() {
1040 t.Fatalf("expected to need idle mark workers: i=%d", i)
1041 }
1042 if !c.AddIdleMarkWorker() {
1043 t.Fatalf("expected to be able to add an idle mark worker: i=%d", i)
1044 }
1045 }
1046 if c.NeedIdleMarkWorker() {
1047 t.Fatalf("expected to not need idle mark workers")
1048 }
1049 if c.AddIdleMarkWorker() {
1050 t.Fatalf("expected to not be able to add an idle mark worker")
1051 }
1052 for i := 0; i < workers; i++ {
1053 c.RemoveIdleMarkWorker()
1054 if !c.NeedIdleMarkWorker() {
1055 t.Fatalf("expected to need idle mark workers after removal: i=%d", i)
1056 }
1057 }
1058 for i := 0; i < workers-1; i++ {
1059 if !c.AddIdleMarkWorker() {
1060 t.Fatalf("expected to be able to add idle mark workers after adding again: i=%d", i)
1061 }
1062 }
1063 for i := 0; i < 10; i++ {
1064 if !c.AddIdleMarkWorker() {
1065 t.Fatalf("expected to be able to add idle mark workers interleaved: i=%d", i)
1066 }
1067 if c.AddIdleMarkWorker() {
1068 t.Fatalf("expected to not be able to add idle mark workers interleaved: i=%d", i)
1069 }
1070 c.RemoveIdleMarkWorker()
1071 }
1072
1073 c.SetMaxIdleMarkWorkers(0)
1074 if c.NeedIdleMarkWorker() {
1075 t.Fatalf("expected to not need idle mark workers after capacity set to 0")
1076 }
1077 if c.AddIdleMarkWorker() {
1078 t.Fatalf("expected to not be able to add idle mark workers after capacity set to 0")
1079 }
1080 for i := 0; i < workers-1; i++ {
1081 c.RemoveIdleMarkWorker()
1082 }
1083 if c.NeedIdleMarkWorker() {
1084 t.Fatalf("expected to not need idle mark workers after capacity set to 0")
1085 }
1086 if c.AddIdleMarkWorker() {
1087 t.Fatalf("expected to not be able to add idle mark workers after capacity set to 0")
1088 }
1089 c.SetMaxIdleMarkWorkers(1)
1090 if !c.NeedIdleMarkWorker() {
1091 t.Fatalf("expected to need idle mark workers after capacity set to 1")
1092 }
1093 if !c.AddIdleMarkWorker() {
1094 t.Fatalf("expected to be able to add idle mark workers after capacity set to 1")
1095 }
1096 }
1097
View as plain text