1
2
3
4
5 package main
6
7 import (
8 "fmt"
9 "math"
10 "os"
11 "runtime"
12 "runtime/debug"
13 "runtime/metrics"
14 "sync"
15 "sync/atomic"
16 "time"
17 "unsafe"
18 )
19
20 func init() {
21 register("GCFairness", GCFairness)
22 register("GCFairness2", GCFairness2)
23 register("GCSys", GCSys)
24 register("GCPhys", GCPhys)
25 register("DeferLiveness", DeferLiveness)
26 register("GCZombie", GCZombie)
27 register("GCMemoryLimit", GCMemoryLimit)
28 register("GCMemoryLimitNoGCPercent", GCMemoryLimitNoGCPercent)
29 }
30
31 func GCSys() {
32 runtime.GOMAXPROCS(1)
33 memstats := new(runtime.MemStats)
34 runtime.GC()
35 runtime.ReadMemStats(memstats)
36 sys := memstats.Sys
37
38 runtime.MemProfileRate = 0
39
40 itercount := 100000
41 for i := 0; i < itercount; i++ {
42 workthegc()
43 }
44
45
46
47 runtime.ReadMemStats(memstats)
48 if sys > memstats.Sys {
49 sys = 0
50 } else {
51 sys = memstats.Sys - sys
52 }
53 if sys > 16<<20 {
54 fmt.Printf("using too much memory: %d bytes\n", sys)
55 return
56 }
57 fmt.Printf("OK\n")
58 }
59
60 var sink []byte
61
62 func workthegc() []byte {
63 sink = make([]byte, 1029)
64 return sink
65 }
66
67 func GCFairness() {
68 runtime.GOMAXPROCS(1)
69 f, err := os.Open("/dev/null")
70 if os.IsNotExist(err) {
71
72
73 fmt.Println("OK")
74 return
75 }
76 if err != nil {
77 fmt.Println(err)
78 os.Exit(1)
79 }
80 for i := 0; i < 2; i++ {
81 go func() {
82 for {
83 f.Write([]byte("."))
84 }
85 }()
86 }
87 time.Sleep(10 * time.Millisecond)
88 fmt.Println("OK")
89 }
90
91 func GCFairness2() {
92
93
94
95 runtime.GOMAXPROCS(1)
96 debug.SetGCPercent(1)
97 var count [3]int64
98 var sink [3]any
99 for i := range count {
100 go func(i int) {
101 for {
102 sink[i] = make([]byte, 1024)
103 atomic.AddInt64(&count[i], 1)
104 }
105 }(i)
106 }
107
108
109
110
111
112
113
114
115
116
117
118 time.Sleep(30 * time.Millisecond)
119 var fail bool
120 for i := range count {
121 if atomic.LoadInt64(&count[i]) == 0 {
122 fail = true
123 }
124 }
125 if fail {
126 time.Sleep(1 * time.Second)
127 for i := range count {
128 if atomic.LoadInt64(&count[i]) == 0 {
129 fmt.Printf("goroutine %d did not run\n", i)
130 return
131 }
132 }
133 }
134 fmt.Println("OK")
135 }
136
137 func GCPhys() {
138
139
140
141
142
143
144
145
146
147 const (
148
149 allocTotal = 32 << 20
150
151
152 maxPageCache = (8 << 10) * 64
153 )
154
155
156
157
158
159 pageSize := os.Getpagesize()
160 var allocChunk int
161 if pageSize <= 8<<10 {
162 allocChunk = 64 << 10
163 } else {
164 allocChunk = 512 << 10
165 }
166 allocs := allocTotal / allocChunk
167
168
169
170 debug.SetGCPercent(100)
171
172
173
174 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
175
176
177
178
179 saved := make([][]byte, allocs/2+1)
180 condemned := make([][]byte, allocs/2)
181 for i := 0; i < allocs; i++ {
182 b := make([]byte, allocChunk)
183 if i%2 == 0 {
184 saved = append(saved, b)
185 } else {
186 condemned = append(condemned, b)
187 }
188 }
189
190
191 runtime.GC()
192
193
194 condemned = nil
195
196
197 runtime.GC()
198
199
200
201
202
203
204
205
206
207 saved = append(saved, make([]byte, allocTotal/2))
208
209
210
211
212
213
214
215
216
217
218
219 var stats runtime.MemStats
220 runtime.ReadMemStats(&stats)
221 heapBacked := stats.HeapSys - stats.HeapReleased - maxPageCache
222
223
224
225
226
227
228
229
230 overuse := (float64(heapBacked) - float64(stats.HeapAlloc)) / float64(stats.HeapAlloc)
231
232
233
234
235
236
237
238 threshold := 0.1 + float64(pageSize)/float64(allocChunk)
239 if overuse <= threshold {
240 fmt.Println("OK")
241 return
242 }
243
244
245
246
247
248
249 fmt.Printf("exceeded physical memory overuse threshold of %3.2f%%: %3.2f%%\n"+
250 "(alloc: %d, goal: %d, sys: %d, rel: %d, objs: %d)\n", threshold*100, overuse*100,
251 stats.HeapAlloc, stats.NextGC, stats.HeapSys, stats.HeapReleased, len(saved))
252 runtime.KeepAlive(saved)
253 runtime.KeepAlive(condemned)
254 }
255
256
257 func DeferLiveness() {
258 var x [10]int
259 escape(&x)
260 fn := func() {
261 if x[0] != 42 {
262 panic("FAIL")
263 }
264 }
265 defer fn()
266
267 x[0] = 42
268 runtime.GC()
269 runtime.GC()
270 runtime.GC()
271 }
272
273
274 func escape(x any) { sink2 = x; sink2 = nil }
275
276 var sink2 any
277
278
279 func GCZombie() {
280
281
282 const size = 190
283 const count = 8192 / size
284 keep := make([]*byte, 0, (count+1)/2)
285 free := make([]uintptr, 0, (count+1)/2)
286 zombies := make([]*byte, 0, len(free))
287 for i := 0; i < count; i++ {
288 obj := make([]byte, size)
289 p := &obj[0]
290 if i%2 == 0 {
291 keep = append(keep, p)
292 } else {
293 free = append(free, uintptr(unsafe.Pointer(p)))
294 }
295 }
296
297
298 runtime.GC()
299
300
301 for _, p := range free {
302 zombies = append(zombies, (*byte)(unsafe.Pointer(p)))
303 }
304
305
306 runtime.GC()
307 println("failed")
308 runtime.KeepAlive(keep)
309 runtime.KeepAlive(zombies)
310 }
311
312 func GCMemoryLimit() {
313 gcMemoryLimit(100)
314 }
315
316 func GCMemoryLimitNoGCPercent() {
317 gcMemoryLimit(-1)
318 }
319
320
321
322
323
324
325 func gcMemoryLimit(gcPercent int) {
326 if oldProcs := runtime.GOMAXPROCS(4); oldProcs < 4 {
327
328
329 println("insufficient CPUs")
330 return
331 }
332 debug.SetGCPercent(gcPercent)
333
334 const myLimit = 256 << 20
335 if limit := debug.SetMemoryLimit(-1); limit != math.MaxInt64 {
336 print("expected MaxInt64 limit, got ", limit, " bytes instead\n")
337 return
338 }
339 if limit := debug.SetMemoryLimit(myLimit); limit != math.MaxInt64 {
340 print("expected MaxInt64 limit, got ", limit, " bytes instead\n")
341 return
342 }
343 if limit := debug.SetMemoryLimit(-1); limit != myLimit {
344 print("expected a ", myLimit, "-byte limit, got ", limit, " bytes instead\n")
345 return
346 }
347
348 target := make(chan int64)
349 var wg sync.WaitGroup
350 wg.Add(1)
351 go func() {
352 defer wg.Done()
353
354 sinkSize := int(<-target / memLimitUnit)
355 for {
356 if len(memLimitSink) != sinkSize {
357 memLimitSink = make([]*[memLimitUnit]byte, sinkSize)
358 }
359 for i := 0; i < len(memLimitSink); i++ {
360 memLimitSink[i] = new([memLimitUnit]byte)
361
362
363 for j := range memLimitSink[i] {
364 memLimitSink[i][j] = 9
365 }
366 }
367
368 runtime.Gosched()
369 select {
370 case newTarget := <-target:
371 if newTarget == math.MaxInt64 {
372 return
373 }
374 sinkSize = int(newTarget / memLimitUnit)
375 default:
376 }
377 }
378 }()
379 var m [2]metrics.Sample
380 m[0].Name = "/memory/classes/total:bytes"
381 m[1].Name = "/memory/classes/heap/released:bytes"
382
383
384
385 maxTarget := int64((myLimit / 10) * 8)
386 increment := int64((myLimit / 10) * 1)
387 for i := increment; i < maxTarget; i += increment {
388 target <- i
389
390
391
392
393
394
395
396
397 bound := int64(myLimit + 16<<20)
398 start := time.Now()
399 for time.Since(start) < 200*time.Millisecond {
400 metrics.Read(m[:])
401 retained := int64(m[0].Value.Uint64() - m[1].Value.Uint64())
402 if retained > bound {
403 print("retained=", retained, " limit=", myLimit, " bound=", bound, "\n")
404 panic("exceeded memory limit by more than bound allows")
405 }
406 runtime.Gosched()
407 }
408 }
409
410 if limit := debug.SetMemoryLimit(math.MaxInt64); limit != myLimit {
411 print("expected a ", myLimit, "-byte limit, got ", limit, " bytes instead\n")
412 return
413 }
414 println("OK")
415 }
416
417
418 const memLimitUnit = 8000
419
420 var memLimitSink []*[memLimitUnit]byte
421
View as plain text