github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/kv/kvserver/spanlatch/signal_test.go (about)

     1  // Copyright 2018 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 spanlatch
    12  
    13  import (
    14  	"sync"
    15  	"sync/atomic"
    16  	"testing"
    17  
    18  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func TestSignal(t *testing.T) {
    22  	var s signal
    23  	require.False(t, s.signaled())
    24  
    25  	s.signal()
    26  	require.True(t, s.signaled())
    27  	require.Equal(t, struct{}{}, <-s.signalChan())
    28  }
    29  
    30  func TestSignalConcurrency(t *testing.T) {
    31  	const trials = 100
    32  	for i := 0; i < trials; i++ {
    33  		var s signal
    34  		var wg sync.WaitGroup
    35  		wg.Add(3)
    36  		go func() {
    37  			defer wg.Done()
    38  			<-s.signalChan()
    39  			require.True(t, s.signaled())
    40  		}()
    41  		go func() {
    42  			defer wg.Done()
    43  			require.False(t, s.signaled())
    44  			s.signal()
    45  			require.True(t, s.signaled())
    46  		}()
    47  		go func() {
    48  			defer wg.Done()
    49  			<-s.signalChan()
    50  			require.True(t, s.signaled())
    51  		}()
    52  		wg.Wait()
    53  		require.True(t, s.signaled())
    54  	}
    55  }
    56  
    57  func BenchmarkSignaled(b *testing.B) {
    58  	var s signal
    59  	s.signal()
    60  	b.ResetTimer()
    61  	for i := 0; i < b.N; i++ {
    62  		_ = s.signaled()
    63  	}
    64  }
    65  
    66  func BenchmarkSignalBeforeChan(b *testing.B) {
    67  	var s signal
    68  	for i := 0; i < b.N; i++ {
    69  		s = signal{} // reset
    70  		s.signal()
    71  	}
    72  }
    73  
    74  func BenchmarkSignalAfterChan(b *testing.B) {
    75  	var s signal
    76  	chans := make([]chan struct{}, b.N)
    77  	for i := range chans {
    78  		chans[i] = make(chan struct{})
    79  	}
    80  	b.ResetTimer()
    81  	for i := 0; i < b.N; i++ {
    82  		s = signal{} // reset
    83  		s.c = chanToPtr(chans[i])
    84  		s.signal()
    85  	}
    86  }
    87  
    88  func BenchmarkInitialChanBeforeSignal(b *testing.B) {
    89  	var s signal
    90  	for i := 0; i < b.N; i++ {
    91  		s = signal{} // reset
    92  		_ = s.signalChan()
    93  	}
    94  }
    95  
    96  func BenchmarkSecondChanBeforeSignal(b *testing.B) {
    97  	var s signal
    98  	_ = s.signalChan()
    99  	b.ResetTimer()
   100  	for i := 0; i < b.N; i++ {
   101  		_ = s.signalChan()
   102  	}
   103  }
   104  
   105  func BenchmarkInitialChanAfterSignal(b *testing.B) {
   106  	var s signal
   107  	s.signal()
   108  	b.ResetTimer()
   109  	for i := 0; i < b.N; i++ {
   110  		s.c = nil
   111  		_ = s.signalChan()
   112  	}
   113  }
   114  
   115  func BenchmarkSecondChanAfterSignal(b *testing.B) {
   116  	var s signal
   117  	s.signal()
   118  	_ = s.signalChan()
   119  	b.ResetTimer()
   120  	for i := 0; i < b.N; i++ {
   121  		_ = s.signalChan()
   122  	}
   123  }
   124  
   125  // The following is a series of benchmarks demonstrating the value of the signal
   126  // type and the fast-path that it provides. Closing channels to signal
   127  // completion of a task is convenient, but in performance critical code paths it
   128  // is essential to have a way to efficiently check for completion before falling
   129  // back to waiting for the channel to close and entering select blocks. The
   130  // benchmarks demonstrate that a channel on its own cannot be used to perform an
   131  // efficient completion check, which is why the signal type mixes channels with
   132  // atomics. The reason for this is that channels are forced to acquire an
   133  // internal mutex before determining that they are closed and can return a zero
   134  // value. This will always be more expensive than a single atomic load.
   135  //
   136  // Results with go1.10.4 on a Mac with a 3.1 GHz Intel Core i7 processor:
   137  //
   138  //   ReadClosedChan-4           24.2ns ± 3%
   139  //   SingleSelectClosedChan-4   24.9ns ± 2%
   140  //   DefaultSelectClosedChan-4  24.6ns ± 1%
   141  //   MultiSelectClosedChan-4    97.9ns ± 2%
   142  //   Signaled-4                 0.35ns ±13%
   143  //
   144  
   145  func BenchmarkReadClosedChan(b *testing.B) {
   146  	c := make(chan struct{})
   147  	close(c)
   148  	for i := 0; i < b.N; i++ {
   149  		<-c
   150  	}
   151  }
   152  
   153  func BenchmarkSingleSelectClosedChan(b *testing.B) {
   154  	c := make(chan struct{})
   155  	close(c)
   156  	//lint:ignore S1000 we don't want this simplified
   157  	for i := 0; i < b.N; i++ {
   158  		select {
   159  		case <-c:
   160  		}
   161  	}
   162  }
   163  
   164  func BenchmarkDefaultSelectClosedChan(b *testing.B) {
   165  	c := make(chan struct{})
   166  	close(c)
   167  	for i := 0; i < b.N; i++ {
   168  		select {
   169  		case <-c:
   170  		default:
   171  		}
   172  	}
   173  }
   174  
   175  func BenchmarkMultiSelectClosedChan(b *testing.B) {
   176  	c, c2 := make(chan struct{}), make(chan struct{})
   177  	close(c)
   178  	for i := 0; i < b.N; i++ {
   179  		select {
   180  		case <-c:
   181  		case <-c2:
   182  		}
   183  	}
   184  }
   185  
   186  func BenchmarkAtomicLoad(b *testing.B) {
   187  	a := int32(1)
   188  	for i := 0; i < b.N; i++ {
   189  		_ = atomic.LoadInt32(&a)
   190  	}
   191  }