github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/util/quotapool/bench_test.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package quotapool_test
    12  
    13  import (
    14  	"context"
    15  	"fmt"
    16  	"runtime"
    17  	"testing"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/util/quotapool"
    20  	"golang.org/x/sync/errgroup"
    21  )
    22  
    23  // BenchmarkIntQuotaPool benchmarks the common case where we have sufficient
    24  // quota available in the pool and we repeatedly acquire and release quota.
    25  func BenchmarkIntPool(b *testing.B) {
    26  	qp := quotapool.NewIntPool("test", 1)
    27  	ctx := context.Background()
    28  	for n := 0; n < b.N; n++ {
    29  		alloc, err := qp.Acquire(ctx, 1)
    30  		if err != nil {
    31  			b.Fatal(err)
    32  		}
    33  		alloc.Release()
    34  	}
    35  	qp.Close("")
    36  }
    37  
    38  func BenchmarkChannelSemaphore(b *testing.B) {
    39  	sem := make(chan struct{}, 1)
    40  	ctx := context.Background()
    41  	for n := 0; n < b.N; n++ {
    42  		select {
    43  		case <-ctx.Done():
    44  		case sem <- struct{}{}:
    45  		}
    46  		select {
    47  		case <-ctx.Done():
    48  		case <-sem:
    49  		}
    50  	}
    51  	close(sem)
    52  }
    53  
    54  // BenchmarkConcurrentIntPool benchmarks concurrent workers in a variety
    55  // of ratios between adequate and inadequate quota to concurrently serve all
    56  // workers with the IntPool.
    57  func BenchmarkConcurrentIntPool(b *testing.B) {
    58  	for _, s := range concurrentBenchSpecs {
    59  		b.Run(s.String(), s.benchmarkIntPool)
    60  	}
    61  }
    62  
    63  // BenchmarkConcurrentChannelSem benchmarks concurrent workers in a variety
    64  // of ratios between adequate and inadequate quota to concurrently serve all
    65  // workers with a channel-based semaphore to compare the performance against
    66  // the IntPool.
    67  func BenchmarkConcurrentChannelSemaphore(b *testing.B) {
    68  	for _, s := range concurrentBenchSpecs {
    69  		b.Run(s.String(), s.benchmarkChannelSem)
    70  	}
    71  }
    72  
    73  // BenchmarkIntQuotaPoolFunc benchmarks the common case where we have sufficient
    74  // quota available in the pool and we repeatedly acquire and release quota.
    75  func BenchmarkIntPoolFunc(b *testing.B) {
    76  	qp := quotapool.NewIntPool("test", 1, logSlowAcquisition)
    77  	ctx := context.Background()
    78  	toAcquire := intRequest(1)
    79  	for n := 0; n < b.N; n++ {
    80  		alloc, err := qp.AcquireFunc(ctx, toAcquire.acquire)
    81  		if err != nil {
    82  			b.Fatal(err)
    83  		} else if acquired := alloc.Acquired(); acquired != 1 {
    84  			b.Fatalf("expected to acquire %d, got %d", 1, acquired)
    85  		}
    86  		alloc.Release()
    87  	}
    88  	qp.Close("")
    89  }
    90  
    91  type concurrentBenchSpec struct {
    92  	workers int
    93  	quota   uint64
    94  }
    95  
    96  func (s concurrentBenchSpec) benchmarkChannelSem(b *testing.B) {
    97  	sem := make(chan struct{}, s.quota)
    98  	g, ctx := errgroup.WithContext(context.Background())
    99  	runWorker := func(workerNum int) {
   100  		g.Go(func() error {
   101  			for i := workerNum; i < b.N; i += s.workers {
   102  				select {
   103  				case <-ctx.Done():
   104  				case sem <- struct{}{}:
   105  				}
   106  				runtime.Gosched()
   107  				select {
   108  				case <-ctx.Done():
   109  				case <-sem:
   110  				}
   111  			}
   112  			return nil
   113  		})
   114  	}
   115  	for i := 0; i < s.workers; i++ {
   116  		runWorker(i)
   117  	}
   118  	if err := g.Wait(); err != nil {
   119  		b.Fatal(err)
   120  	}
   121  	close(sem)
   122  }
   123  
   124  func (s concurrentBenchSpec) benchmarkIntPool(b *testing.B) {
   125  	qp := quotapool.NewIntPool("test", s.quota, logSlowAcquisition)
   126  	g, ctx := errgroup.WithContext(context.Background())
   127  	runWorker := func(workerNum int) {
   128  		g.Go(func() error {
   129  			for i := workerNum; i < b.N; i += s.workers {
   130  				alloc, err := qp.Acquire(ctx, 1)
   131  				if err != nil {
   132  					b.Fatal(err)
   133  				}
   134  				runtime.Gosched()
   135  				alloc.Release()
   136  			}
   137  			return nil
   138  		})
   139  	}
   140  	for i := 0; i < s.workers; i++ {
   141  		runWorker(i)
   142  	}
   143  	if err := g.Wait(); err != nil {
   144  		b.Fatal(err)
   145  	}
   146  	qp.Close("")
   147  }
   148  
   149  func (s concurrentBenchSpec) String() string {
   150  	return fmt.Sprintf("workers=%d,quota=%d", s.workers, s.quota)
   151  }
   152  
   153  var concurrentBenchSpecs = []concurrentBenchSpec{
   154  	{1, 1},
   155  	{2, 2},
   156  	{8, 4},
   157  	{128, 4},
   158  	{512, 128},
   159  	{512, 513},
   160  	{512, 511},
   161  	{1024, 4},
   162  	{1024, 4096},
   163  }
   164  
   165  // intRequest is a wrapper to create a IntRequestFunc from an int64.
   166  type intRequest uint64
   167  
   168  func (ir intRequest) acquire(_ context.Context, pi quotapool.PoolInfo) (took uint64, err error) {
   169  	if uint64(ir) < pi.Available {
   170  		return 0, quotapool.ErrNotEnoughQuota
   171  	}
   172  	return uint64(ir), nil
   173  }