@@ -166,6 +166,7 @@ func (p *Pool) getSlow() (x interface{}) {
166
166
last := len (l .shared ) - 1
167
167
if last >= 0 {
168
168
x = l .shared [last ]
169
+ l .shared [last ] = nil
169
170
l .shared = l .shared [:last ]
170
171
l .Unlock ()
171
172
break
@@ -205,7 +206,7 @@ func (p *Pool) pinSlow() *poolLocal {
205
206
return indexLocal (l , pid )
206
207
}
207
208
if p .local == nil {
208
- allPools = append ( allPools , p )
209
+ allPools . pushBack ( p )
209
210
}
210
211
// If GOMAXPROCS changes between GCs, we re-allocate the array and lose the old one.
211
212
size := runtime .GOMAXPROCS (0 )
@@ -215,32 +216,56 @@ func (p *Pool) pinSlow() *poolLocal {
215
216
return & local [pid ]
216
217
}
217
218
219
+ var cleanupCount uint
220
+
218
221
func poolCleanup () {
219
222
// This function is called with the world stopped, at the beginning of a garbage collection.
220
223
// It must not allocate and probably should not call any runtime functions.
221
- // Defensively zero out everything, 2 reasons:
224
+ // When pools or poolLocals need to be emptied we defensively zero out everything for 2 reasons:
222
225
// 1. To prevent false retention of whole Pools.
223
226
// 2. If GC happens while a goroutine works with l.shared in Put/Get,
224
227
// it will retain whole Pool. So next cycle memory consumption would be doubled.
225
- for i , p := range allPools {
226
- allPools [i ] = nil
228
+ // Under normal circumstances we don't delete all pools, instead we drop half of the poolLocals
229
+ // every cycle, and whole pools if all poolLocals are empty when starting the cleanup (this means
230
+ // that a non-empty Pool will take 3 GC cycles to be completely deleted: the first will delete
231
+ // half of the poolLocals, the second the remaining half and the third the now empty Pool itself).
232
+
233
+ // deleteAll is a placeholder for dynamically controlling whether pools are aggressively
234
+ // or partially cleaned up. If true, all pools are emptied every GC; if false only half of
235
+ // the poolLocals are dropped. For now it is a constant so that it can be optimized away
236
+ // at compile-time; ideally the runtime should decide whether to set deleteAll to true
237
+ // based on memory pressure (see #29696).
238
+ const deleteAll = false
239
+
240
+ for e := allPools .front (); e != nil ; e = e .nextElement () {
241
+ p := e .value
242
+ empty := true
227
243
for i := 0 ; i < int (p .localSize ); i ++ {
228
244
l := indexLocal (p .local , i )
245
+ if l .private != nil || len (l .shared ) > 0 {
246
+ empty = false
247
+ }
248
+ if i % 2 == int (cleanupCount % 2 ) && ! deleteAll {
249
+ continue
250
+ }
229
251
l .private = nil
230
252
for j := range l .shared {
231
253
l .shared [j ] = nil
232
254
}
233
255
l .shared = nil
234
256
}
235
- p .local = nil
236
- p .localSize = 0
257
+ if empty || deleteAll {
258
+ p .local = nil
259
+ p .localSize = 0
260
+ allPools .remove (e )
261
+ }
237
262
}
238
- allPools = [] * Pool {}
263
+ cleanupCount ++
239
264
}
240
265
241
266
var (
242
267
allPoolsMu Mutex
243
- allPools [] * Pool
268
+ allPools list
244
269
)
245
270
246
271
func init () {
@@ -256,3 +281,63 @@ func indexLocal(l unsafe.Pointer, i int) *poolLocal {
256
281
func runtime_registerPoolCleanup (cleanup func ())
257
282
func runtime_procPin () int
258
283
func runtime_procUnpin ()
284
+
285
+ // Stripped-down and specialized version of container/list (to avoid using interface{}
286
+ // casts, since they can allocate and allocation is forbidden in poolCleanup). Note that
287
+ // these functions are so small and simple that they all end up completely inlined.
288
+ // pushBack may potentially be made atomic so that allPoolsMu can be removed.
289
+
290
+ type element struct {
291
+ next , prev * element
292
+ list * list
293
+ value * Pool
294
+ }
295
+
296
+ func (e * element ) nextElement () * element {
297
+ if p := e .next ; e .list != nil && p != & e .list .root {
298
+ return p
299
+ }
300
+ return nil
301
+ }
302
+
303
+ type list struct {
304
+ root element
305
+ }
306
+
307
+ func (l * list ) front () * element {
308
+ if l .root .next == & l .root {
309
+ return nil
310
+ }
311
+ return l .root .next
312
+ }
313
+
314
+ func (l * list ) lazyInit () {
315
+ if l .root .next == nil {
316
+ l .root .next = & l .root
317
+ l .root .prev = & l .root
318
+ }
319
+ }
320
+
321
+ func (l * list ) insert (e , at * element ) {
322
+ n := at .next
323
+ at .next = e
324
+ e .prev = at
325
+ e .next = n
326
+ n .prev = e
327
+ e .list = l
328
+ }
329
+
330
+ func (l * list ) remove (e * element ) {
331
+ if e .list == l {
332
+ e .prev .next = e .next
333
+ e .next .prev = e .prev
334
+ e .next = nil
335
+ e .prev = nil
336
+ e .list = nil
337
+ }
338
+ }
339
+
340
+ func (l * list ) pushBack (v * Pool ) {
341
+ l .lazyInit ()
342
+ l .insert (& element {value : v }, l .root .prev )
343
+ }
0 commit comments