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