github.com/searKing/golang/go@v1.2.117/exp/sync/fixedpool_test.go (about)

     1  // Copyright 2022 The searKing Author. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package sync_test
     6  
     7  import (
     8  	"fmt"
     9  	"path"
    10  	"path/filepath"
    11  	"runtime"
    12  	"runtime/debug"
    13  	"sort"
    14  	"strconv"
    15  	"sync/atomic"
    16  	"testing"
    17  	"time"
    18  
    19  	sync_ "github.com/searKing/golang/go/exp/sync"
    20  	runtime_ "github.com/searKing/golang/go/runtime"
    21  )
    22  
    23  func fixGC() {
    24  	// TODO: Fix #45315 and remove this extra call.
    25  	//
    26  	// Unfortunately, it's possible for the sweep termination condition
    27  	// to flap, so with just one runtime.GC call, a freed object could be
    28  	// missed, leading this test to fail. A second call reduces the chance
    29  	// of this happening to zero, because sweeping actually has to finish
    30  	// to move on to the next GC, during which nothing will happen.
    31  	//
    32  	// See https://github.com/golang/go/issues/46500 and
    33  	// https://github.com/golang/go/issues/45315 for more details.
    34  	runtime.GOMAXPROCS(1)
    35  }
    36  func caller() string {
    37  	function, file, line := runtime_.GetCallerFuncFileLine(3)
    38  	return fmt.Sprintf("%s() %s:%d", path.Base(function), filepath.Base(file), line)
    39  }
    40  
    41  func testFixedPoolLenAndCap[E any](t *testing.T, p *sync_.FixedPool[E], l, c int) {
    42  	gotLen := p.Len()
    43  	gotCap := p.Cap()
    44  	if (gotLen != l && c >= 0) || (gotCap != c && c >= 0) {
    45  		t.Fatalf("%s, got %d|%d; want %d|%d", caller(), gotLen, gotCap, l, c)
    46  	}
    47  }
    48  
    49  func TestNewFixedPool(t *testing.T) {
    50  	fixGC()
    51  
    52  	// disable GC so we can control when it happens.
    53  	defer debug.SetGCPercent(debug.SetGCPercent(-1))
    54  	var i int
    55  	f := func() string {
    56  		defer func() { i++ }()
    57  		return strconv.Itoa(i)
    58  	}
    59  	var p = sync_.NewFixedPool[string](f, 2)
    60  	testFixedPoolLenAndCap(t, p, 2, 2)
    61  	if g := p.TryGet(); g == nil || g.Value != "0" {
    62  		t.Fatalf("got %#v; want 0", g)
    63  	}
    64  	testFixedPoolLenAndCap(t, p, 1, 2)
    65  	p.Emplace("a")
    66  	testFixedPoolLenAndCap(t, p, 2, 3)
    67  	p.Emplace("b")
    68  	testFixedPoolLenAndCap(t, p, 3, 4)
    69  	if g := p.TryGet(); g == nil || g.Value != "1" {
    70  		t.Fatalf("got %#v; want 1", g)
    71  	}
    72  	if g := p.Get(); g == nil || g.Value != "a" {
    73  		t.Fatalf("got %#v; want a", g)
    74  	}
    75  	testFixedPoolLenAndCap(t, p, 1, 4)
    76  	if g := p.Get(); g.Value != "b" {
    77  		t.Fatalf("got %#v; want b", g)
    78  	}
    79  	testFixedPoolLenAndCap(t, p, 0, 4)
    80  	if g := p.TryGet(); g != nil {
    81  		t.Fatalf("got %#v; want nil", g)
    82  	}
    83  	// After one GC, the victim cache should keep them alive.
    84  	runtime.GC()
    85  	// drop all the items taken by Get and not be referenced by any
    86  	testFixedPoolLenAndCap(t, p, 2, 2)
    87  	// A second GC should drop the victim cache.
    88  	runtime.GC()
    89  	testFixedPoolLenAndCap(t, p, 2, 2)
    90  
    91  	// Put in a large number of items, so they spill into
    92  	// stealable space.
    93  	n := 100
    94  	for i := 0; i < n; i++ {
    95  		p.Emplace("c")
    96  		testFixedPoolLenAndCap(t, p, i+1+2, i+1+2)
    97  	}
    98  	testFixedPoolLenAndCap(t, p, 102, 102)
    99  	for i := 0; i < n; i++ {
   100  		if g := p.Get(); g == nil {
   101  			t.Fatalf("got empty")
   102  		}
   103  	}
   104  	testFixedPoolLenAndCap(t, p, 2, 102)
   105  	if g := p.TryGet(); g == nil {
   106  		t.Fatalf("got empty")
   107  	}
   108  	testFixedPoolLenAndCap(t, p, 1, 102)
   109  	// After one GC, the victim cache should keep them alive.
   110  	runtime.GC()
   111  	// drop all the items taken by Get and not be referenced by any
   112  	testFixedPoolLenAndCap(t, p, 3, 3)
   113  	// A second GC should drop the victim cache.
   114  	runtime.GC()
   115  	testFixedPoolLenAndCap(t, p, 2, 2)
   116  }
   117  func TestNewCachePool(t *testing.T) {
   118  	fixGC()
   119  	// disable GC so we can control when it happens.
   120  	defer debug.SetGCPercent(debug.SetGCPercent(-1))
   121  	var p = sync_.NewCachedPool[string](nil)
   122  	if p.TryGet() != nil {
   123  		t.Fatal("expected empty")
   124  	}
   125  	p.Emplace("a")
   126  	testFixedPoolLenAndCap(t, p, 1, 1)
   127  	p.Emplace("b")
   128  	testFixedPoolLenAndCap(t, p, 2, 2)
   129  	if g := p.Get(); g.Value != "a" {
   130  		t.Fatalf("got %#v; want a", g)
   131  	}
   132  	testFixedPoolLenAndCap(t, p, 1, 2)
   133  	if g := p.Get(); g.Value != "b" {
   134  		t.Fatalf("got %#v; want b", g)
   135  	}
   136  	testFixedPoolLenAndCap(t, p, 0, 2)
   137  	if g := p.TryGet(); g != nil {
   138  		t.Fatalf("got %#v; want nil", g)
   139  	}
   140  	testFixedPoolLenAndCap(t, p, 0, 2)
   141  
   142  	// Put in a large number of items, so they spill into
   143  	// stealable space.
   144  	n := 100
   145  	for i := 0; i < n; i++ {
   146  		p.Emplace("c")
   147  		testFixedPoolLenAndCap(t, p, i+1, i+1+2)
   148  	}
   149  	testFixedPoolLenAndCap(t, p, 100, 102)
   150  	for i := 0; i < n; i++ {
   151  		if g := p.Get(); g.Value != "c" {
   152  			t.Fatalf("got %#v; want a", g)
   153  		}
   154  	}
   155  	testFixedPoolLenAndCap(t, p, 0, 102)
   156  	if g := p.TryGet(); g != nil {
   157  		t.Fatalf("got %#v; want nil", g)
   158  	}
   159  	testFixedPoolLenAndCap(t, p, 0, 102)
   160  }
   161  
   162  func TestNewTempPool(t *testing.T) {
   163  	fixGC()
   164  	// disable GC so we can control when it happens.
   165  	defer debug.SetGCPercent(debug.SetGCPercent(-1))
   166  	var p = sync_.NewTempPool[string](nil)
   167  	if p.TryGet() != nil {
   168  		t.Fatal("expected empty")
   169  	}
   170  	testFixedPoolLenAndCap(t, p, 0, 0)
   171  	p.Emplace("a")
   172  
   173  	testFixedPoolLenAndCap(t, p, 1, 1)
   174  	p.Emplace("b")
   175  	testFixedPoolLenAndCap(t, p, 2, 2)
   176  	if g := p.Get(); g.Value != "a" {
   177  		t.Fatalf("got %#v; want a", g)
   178  	}
   179  	testFixedPoolLenAndCap(t, p, 1, 2)
   180  	if g := p.Get(); g.Value != "b" {
   181  		t.Fatalf("got %#v; want b", g)
   182  	}
   183  	testFixedPoolLenAndCap(t, p, 0, 2)
   184  
   185  	// Put in a large number of items, so they spill into
   186  	// stealable space.
   187  	for i := 0; i < 100; i++ {
   188  		p.Emplace("c")
   189  		testFixedPoolLenAndCap(t, p, i+1, i+1+2)
   190  	}
   191  	testFixedPoolLenAndCap(t, p, 100, 102)
   192  	// After one GC, the victim cache should keep them alive.
   193  	runtime.GC()
   194  	// drop all the items taken by Get and not be referenced by any
   195  	testFixedPoolLenAndCap(t, p, 100, 100)
   196  	if g := p.Get(); g.Value != "c" {
   197  		t.Fatalf("got %#v; want c after GC", g)
   198  	}
   199  	testFixedPoolLenAndCap(t, p, 99, 100)
   200  	// A second GC should drop the victim cache.
   201  	runtime.GC()
   202  	testFixedPoolLenAndCap(t, p, 0, 0)
   203  	if g := p.TryGet(); g != nil {
   204  		t.Fatalf("got %#v; want nil after second GC", g)
   205  	}
   206  	testFixedPoolLenAndCap(t, p, 0, 0)
   207  }
   208  
   209  func TestFixedPoolNilNew(t *testing.T) {
   210  	fixGC()
   211  	// disable GC so we can control when it happens.
   212  	defer debug.SetGCPercent(debug.SetGCPercent(-1))
   213  	const LEN = 10
   214  	const CAP = 20
   215  	var p = (&sync_.FixedPool[string]{
   216  		New:             nil,
   217  		MinResidentSize: 0,
   218  		MaxResidentSize: LEN,
   219  		MaxCapacity:     CAP,
   220  	}).Init()
   221  
   222  	testFixedPoolLenAndCap(t, p, 0, 0)
   223  
   224  	if p.TryGet() != nil {
   225  		t.Fatal("expected empty")
   226  	}
   227  	p.Emplace("a")
   228  	testFixedPoolLenAndCap(t, p, 1, 1)
   229  	p.Emplace("b")
   230  	testFixedPoolLenAndCap(t, p, 2, 2)
   231  	if g := p.Get(); g.Value != "a" {
   232  		t.Fatalf("got %#v; want a", g)
   233  	}
   234  	testFixedPoolLenAndCap(t, p, 1, 2)
   235  	if g := p.Get(); g.Value != "b" {
   236  		t.Fatalf("got %#v; want b", g)
   237  	}
   238  	testFixedPoolLenAndCap(t, p, 0, 2)
   239  	if g := p.TryGet(); g != nil {
   240  		t.Fatalf("got %#v; want nil", g)
   241  	}
   242  	testFixedPoolLenAndCap(t, p, 0, 2)
   243  
   244  	// Put in a large number of items, so they spill into
   245  	// stealable space.
   246  	for i := 0; i < 100; i++ {
   247  		p.Emplace("c")
   248  		testFixedPoolLenAndCap(t, p, i+1, i+1+2)
   249  	}
   250  	testFixedPoolLenAndCap(t, p, 100, 102)
   251  	// After one GC, the victim cache should keep them alive.
   252  	runtime.GC()
   253  	testFixedPoolLenAndCap(t, p, 100, 100)
   254  	if g := p.Get(); g.Value != "c" {
   255  		t.Fatalf("got %#v; want c after GC", g)
   256  	}
   257  	testFixedPoolLenAndCap(t, p, 99, 100)
   258  	// A second GC should drop the victim cache, try put into local first.
   259  	runtime.GC()
   260  	testFixedPoolLenAndCap(t, p, LEN, LEN)
   261  
   262  	// drain keep-alive cache
   263  	for i := 0; i < LEN; i++ {
   264  		if g := p.Get(); g == nil || g.Value != "c" {
   265  			t.Fatalf("#%d: got %#v; want c after GC", i, g)
   266  		}
   267  	}
   268  	if g := p.TryGet(); g != nil {
   269  		t.Fatalf("got %#v; want nil after second GC", g)
   270  	}
   271  	testFixedPoolLenAndCap(t, p, 0, LEN)
   272  	// After one GC, the victim cache should keep them alive.
   273  	// After one GC, the got object will be GC, as no reference
   274  	runtime.GC()
   275  	testFixedPoolLenAndCap(t, p, LEN, LEN)
   276  	// A second GC should drop the victim cache, try put into local first.
   277  	runtime.GC()
   278  	testFixedPoolLenAndCap(t, p, LEN, LEN)
   279  }
   280  
   281  func TestFixedPoolNew(t *testing.T) {
   282  	fixGC()
   283  	// disable GC so we can control when it happens.
   284  	defer debug.SetGCPercent(debug.SetGCPercent(-1))
   285  
   286  	const MinLen = 2
   287  	const LEN = 10
   288  	const CAP = 20
   289  	i := 0
   290  	var p = (&sync_.FixedPool[int]{
   291  		New: func() int {
   292  			i++
   293  			return i
   294  		},
   295  		MinResidentSize: MinLen,
   296  		MaxResidentSize: LEN,
   297  		MaxCapacity:     CAP,
   298  	}).Init()
   299  	testFixedPoolLenAndCap(t, p, MinLen, MinLen)
   300  
   301  	if v := p.Get(); v.Value != 1 {
   302  		t.Fatalf("got %v; want 1", v.Value)
   303  	}
   304  	if v := p.Get(); v.Value != 2 {
   305  		t.Fatalf("got %v; want 2", v.Value)
   306  	}
   307  
   308  	p.Emplace(42)
   309  	if v := p.Get(); v.Value != 42 {
   310  		t.Fatalf("got %v; want 42", v)
   311  	}
   312  
   313  	if v := p.Get(); v.Value != 3 {
   314  		t.Fatalf("got %v; want 3", v)
   315  	}
   316  }
   317  
   318  func TestFixedPoolGCRetryPut(t *testing.T) {
   319  	// disable GC so we can control when it happens.
   320  	defer debug.SetGCPercent(debug.SetGCPercent(-1))
   321  	const LEN = 1
   322  	const CAP = 2
   323  	var p = (&sync_.FixedPool[string]{
   324  		New:             nil,
   325  		MinResidentSize: 0,
   326  		MaxResidentSize: LEN,
   327  		MaxCapacity:     CAP,
   328  	}).Init()
   329  
   330  	testFixedPoolLenAndCap(t, p, 0, 0)
   331  
   332  	if p.TryGet() != nil {
   333  		t.Fatal("expected empty")
   334  	}
   335  
   336  	// Put in a large number of items, so they spill into
   337  	// stealable space.
   338  	var N = 4
   339  	for i := 0; i < N; i++ {
   340  		p.Emplace(strconv.Itoa(i))
   341  		testFixedPoolLenAndCap(t, p, i+1, i+1)
   342  	}
   343  	testFixedPoolLenAndCap(t, p, N, N)
   344  	// After one GC, the victim cache should keep them alive.
   345  	runtime.GC()
   346  	testFixedPoolLenAndCap(t, p, N, N)
   347  	if g := p.Get(); g.Value != "0" {
   348  		t.Fatalf("got %#v; want c after GC", g)
   349  	}
   350  	testFixedPoolLenAndCap(t, p, N-1, N)
   351  	// A second GC should drop the victim cache, try put into local first.
   352  	runtime.GC()
   353  	testFixedPoolLenAndCap(t, p, LEN, LEN)
   354  
   355  	// drain keep-alive cache
   356  	for i := 1; i < LEN+1; i++ {
   357  		if g := p.Get(); g == nil {
   358  			t.Fatalf("#%d: got nil; want %q after GC", i, strconv.Itoa(i))
   359  		}
   360  	}
   361  	testFixedPoolLenAndCap(t, p, 0, LEN)
   362  	{
   363  		if g := p.TryGet(); g != nil {
   364  			t.Fatalf("got %#v; want nil after second GC", g.Value)
   365  		}
   366  	}
   367  	testFixedPoolLenAndCap(t, p, 0, LEN)
   368  	// After one GC, the victim cache should keep them alive.
   369  	runtime.GC()
   370  	testFixedPoolLenAndCap(t, p, LEN, LEN)
   371  	// A second GC should drop the victim cache, try put into local first.
   372  	runtime.GC()
   373  	testFixedPoolLenAndCap(t, p, LEN, LEN)
   374  	{
   375  		if g := p.TryGet(); g == nil {
   376  			t.Fatalf("got nil; want %q after GC", g.Value)
   377  		}
   378  	}
   379  	testFixedPoolLenAndCap(t, p, 0, LEN)
   380  }
   381  
   382  func TestFixedPoolGCReFillLocal(t *testing.T) {
   383  	// disable GC so we can control when it happens.
   384  	defer debug.SetGCPercent(debug.SetGCPercent(-1))
   385  	const LEN = 1
   386  	const CAP = 2
   387  	var p = (&sync_.FixedPool[string]{
   388  		New:             nil,
   389  		MinResidentSize: 0,
   390  		MaxResidentSize: LEN,
   391  		MaxCapacity:     CAP,
   392  	}).Init()
   393  	// Put in a large number of items, so they spill into
   394  	// stealable space.
   395  	for i := 0; i < CAP*2; i++ {
   396  		p.Emplace(strconv.Itoa(i))
   397  	}
   398  	testFixedPoolLenAndCap(t, p, CAP*2, CAP*2)
   399  
   400  	// drain all cache
   401  	for i := 0; i < 2*CAP; i++ {
   402  		g := p.Get()
   403  		if i < LEN {
   404  			if g == nil || g.Value != strconv.Itoa(i) {
   405  				t.Fatalf("#%d: got %#v; want %q after GC", i, g, strconv.Itoa(i))
   406  			}
   407  		} else {
   408  			if g == nil {
   409  				t.Fatalf("#%d: got %#v; want %q after GC", i, g, strconv.Itoa(i))
   410  			}
   411  		}
   412  		testFixedPoolLenAndCap(t, p, CAP*2-i-1, CAP*2)
   413  	}
   414  	testFixedPoolLenAndCap(t, p, 0, CAP*2)
   415  	if g := p.TryGet(); g != nil {
   416  		t.Fatalf("got %#v; want nil after second GC", g)
   417  	}
   418  	testFixedPoolLenAndCap(t, p, 0, CAP*2)
   419  
   420  	// After one GC, the victim cache should keep them alive.
   421  	runtime.GC()
   422  	testFixedPoolLenAndCap(t, p, LEN, LEN)
   423  	// A second GC should drop the victim cache, try put into local first.
   424  	runtime.GC()
   425  	testFixedPoolLenAndCap(t, p, LEN, LEN)
   426  
   427  	// drain all cache
   428  	for i := 0; i < LEN; i++ {
   429  		g := p.Get()
   430  		if g == nil {
   431  			t.Fatalf("#%d: got nil; want not nil after GC", i)
   432  		}
   433  	}
   434  	testFixedPoolLenAndCap(t, p, 0, LEN)
   435  	if g := p.TryGet(); g != nil {
   436  		t.Fatalf("got %#v; want nil after second GC", g)
   437  	}
   438  	testFixedPoolLenAndCap(t, p, 0, LEN)
   439  }
   440  
   441  // Test that Pool does not hold pointers to previously cached resources.
   442  func TestFixedPoolGC(t *testing.T) {
   443  	testFixedPool(t, true)
   444  }
   445  
   446  // Test that Pool releases resources on GC.
   447  func TestFixedPoolRelease(t *testing.T) {
   448  	testFixedPool(t, false)
   449  }
   450  
   451  func testFixedPool(t *testing.T, drain bool) {
   452  	var p sync_.FixedPool[*string]
   453  	const N = 100
   454  loop:
   455  	for try := 0; try < 3; try++ {
   456  		if try == 1 && testing.Short() {
   457  			testFixedPoolLenAndCap(t, &p, 0, 0)
   458  			break
   459  		}
   460  		var fin, fin1 uint32
   461  		for i := 0; i < N; i++ {
   462  			v := new(string)
   463  			runtime.SetFinalizer(v, func(vv *string) {
   464  				atomic.AddUint32(&fin, 1)
   465  			})
   466  			p.Emplace(v)
   467  		}
   468  		if drain {
   469  			for i := 0; i < N; i++ {
   470  				p.Get()
   471  			}
   472  		}
   473  		for i := 0; i < 5; i++ {
   474  			runtime.GC()
   475  			time.Sleep(time.Duration(i*100+10) * time.Millisecond)
   476  			// 1 pointer can remain on stack or elsewhere
   477  			if fin1 = atomic.LoadUint32(&fin); fin1 >= N-1 {
   478  				continue loop
   479  			}
   480  		}
   481  		t.Fatalf("only %v out of %v resources are finalized on try %v", fin1, N, try)
   482  	}
   483  }
   484  
   485  func TestFixedPoolStress(t *testing.T) {
   486  	const P = 10
   487  	N := int(1e6)
   488  	if testing.Short() {
   489  		N /= 100
   490  	}
   491  	var p sync_.FixedPool[any]
   492  	done := make(chan bool)
   493  	for i := 0; i < P; i++ {
   494  		go func() {
   495  			var v any = 0
   496  			for j := 0; j < N; j++ {
   497  				if v == nil {
   498  					v = 0
   499  				}
   500  				p.Emplace(v)
   501  				e := p.Get()
   502  				if e != nil && e.Value != 0 {
   503  					t.Errorf("expect 0, got %v", v)
   504  					break
   505  				}
   506  			}
   507  			done <- true
   508  		}()
   509  	}
   510  	for i := 0; i < P; i++ {
   511  		<-done
   512  	}
   513  }
   514  
   515  func BenchmarkFixedPool(b *testing.B) {
   516  	var p sync_.FixedPool[int]
   517  	b.RunParallel(func(pb *testing.PB) {
   518  		for pb.Next() {
   519  			p.Emplace(1)
   520  			p.Get()
   521  		}
   522  	})
   523  }
   524  
   525  func BenchmarkPoolOverflow(b *testing.B) {
   526  	var p sync_.FixedPool[int]
   527  	b.RunParallel(func(pb *testing.PB) {
   528  		for pb.Next() {
   529  			for b := 0; b < 100; b++ {
   530  				p.Emplace(1)
   531  			}
   532  			for b := 0; b < 100; b++ {
   533  				p.Get()
   534  			}
   535  		}
   536  	})
   537  }
   538  
   539  // Simulate object starvation in order to force Ps to steal items
   540  // from other Ps.
   541  func BenchmarkPoolStarvation(b *testing.B) {
   542  	var p sync_.FixedPool[int]
   543  	count := 100
   544  	// Reduce number of putted items by 33 %. It creates items starvation
   545  	// that force P-local storage to steal items from other Ps.
   546  	countStarved := count - int(float32(count)*0.33)
   547  	b.RunParallel(func(pb *testing.PB) {
   548  		for pb.Next() {
   549  			for b := 0; b < countStarved; b++ {
   550  				p.Emplace(1)
   551  			}
   552  			for b := 0; b < count; b++ {
   553  				p.Get()
   554  			}
   555  		}
   556  	})
   557  }
   558  
   559  var globalSink any
   560  
   561  func BenchmarkPoolSTW(b *testing.B) {
   562  	// Take control of GC.
   563  	defer debug.SetGCPercent(debug.SetGCPercent(-1))
   564  
   565  	var mstats runtime.MemStats
   566  	var pauses []uint64
   567  
   568  	var p sync_.FixedPool[any]
   569  	for i := 0; i < b.N; i++ {
   570  		// Put a large number of items into a pool.
   571  		const N = 100000
   572  		var item any = 42
   573  		for i := 0; i < N; i++ {
   574  			p.Emplace(item)
   575  		}
   576  		// Do a GC.
   577  		runtime.GC()
   578  		// Record pause time.
   579  		runtime.ReadMemStats(&mstats)
   580  		pauses = append(pauses, mstats.PauseNs[(mstats.NumGC+255)%256])
   581  	}
   582  
   583  	// Get pause time stats.
   584  	sort.Slice(pauses, func(i, j int) bool { return pauses[i] < pauses[j] })
   585  	var total uint64
   586  	for _, ns := range pauses {
   587  		total += ns
   588  	}
   589  	// ns/op for this benchmark is average STW time.
   590  	b.ReportMetric(float64(total)/float64(b.N), "ns/op")
   591  	b.ReportMetric(float64(pauses[len(pauses)*95/100]), "p95-ns/STW")
   592  	b.ReportMetric(float64(pauses[len(pauses)*50/100]), "p50-ns/STW")
   593  }
   594  
   595  func BenchmarkPoolExpensiveNew(b *testing.B) {
   596  	// Populate a pool with items that are expensive to construct
   597  	// to stress pool cleanup and subsequent reconstruction.
   598  
   599  	// Create a ballast so the GC has a non-zero heap size and
   600  	// runs at reasonable times.
   601  	globalSink = make([]byte, 8<<20)
   602  	defer func() { globalSink = nil }()
   603  
   604  	// Create a pool that's "expensive" to fill.
   605  	var p sync_.FixedPool[any]
   606  	var nNew uint64
   607  	p.New = func() any {
   608  		atomic.AddUint64(&nNew, 1)
   609  		time.Sleep(time.Millisecond)
   610  		return 42
   611  	}
   612  	var mstats1, mstats2 runtime.MemStats
   613  	runtime.ReadMemStats(&mstats1)
   614  	b.RunParallel(func(pb *testing.PB) {
   615  		// Simulate 100X the number of goroutines having items
   616  		// checked out from the Pool simultaneously.
   617  		items := make([]*sync_.FixedPoolElement[any], 100)
   618  		var sink []byte
   619  		for pb.Next() {
   620  			// Stress the pool.
   621  			for i := range items {
   622  				items[i] = p.Get()
   623  				// Simulate doing some work with this
   624  				// item checked out.
   625  				sink = make([]byte, 32<<10)
   626  			}
   627  			for i, v := range items {
   628  				p.Put(v)
   629  				items[i] = nil
   630  			}
   631  		}
   632  		_ = sink
   633  	})
   634  	runtime.ReadMemStats(&mstats2)
   635  
   636  	b.ReportMetric(float64(mstats2.NumGC-mstats1.NumGC)/float64(b.N), "GCs/op")
   637  	b.ReportMetric(float64(nNew)/float64(b.N), "New/op")
   638  }