github.com/benz9527/xboot@v0.0.0-20240504061247-c23f15593274/lib/kv/thread_safe_map_test.go (about)

     1  package kv
     2  
     3  import (
     4  	"fmt"
     5  	"math/bits"
     6  	randv2 "math/rand/v2"
     7  	"strconv"
     8  	"sync"
     9  	"sync/atomic"
    10  	"testing"
    11  
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func TestThreadSafeMap_SimpleCRUD(t *testing.T) {
    16  	keys := genStrKeys(8, 10000)
    17  	vals := make([]int, 0, len(keys))
    18  	m := make(map[string]int, len(keys))
    19  	_m := NewThreadSafeMap[string, int](WithThreadSafeMapInitCap[string, int](10_000),
    20  		WithThreadSafeMapCloseableItemCheck[string, int](),
    21  	)
    22  	for i, key := range keys {
    23  		m[key] = i
    24  		vals = append(vals, i)
    25  	}
    26  	_m.Replace(m)
    27  
    28  	_keys := _m.ListKeys()
    29  	require.Equal(t, len(keys), len(_keys))
    30  	require.ElementsMatch(t, keys, _keys)
    31  
    32  	_vals := _m.ListValues()
    33  	require.ElementsMatch(t, vals, _vals)
    34  
    35  	i := 1001
    36  	res, exists := _m.Get(keys[i])
    37  	require.True(t, exists)
    38  	require.Equal(t, i, res)
    39  
    40  	res, err := _m.Delete(keys[i])
    41  	require.NoError(t, err)
    42  	require.Equal(t, i, res)
    43  
    44  	err = _m.AddOrUpdate(keys[i], i)
    45  	require.NoError(t, err)
    46  
    47  	_keys = _m.ListKeys()
    48  	require.Equal(t, len(keys), len(_keys))
    49  	require.ElementsMatch(t, keys, _keys)
    50  
    51  	_vals = _m.ListValues()
    52  	require.ElementsMatch(t, vals, _vals)
    53  
    54  	err = _m.Purge()
    55  	require.NoError(t, err)
    56  }
    57  
    58  func BenchmarkStringThreadSafeMap(b *testing.B) {
    59  	const strKeyLen = 8
    60  	sizes := []int{16, 128, 1024, 8192, 131072, 1 << 20}
    61  	for _, n := range sizes {
    62  		b.Run("ThreadSafeMap n="+strconv.Itoa(n), func(bb *testing.B) {
    63  			keys := genStrKeys(strKeyLen, n)
    64  			n := uint32(len(keys))
    65  			mod := n - 1 // power of 2 fast modulus
    66  			require.Equal(bb, 1, bits.OnesCount32(n))
    67  			m := NewThreadSafeMap[string, string](WithThreadSafeMapInitCap[string, string](n))
    68  			bb.ResetTimer()
    69  			for _, k := range keys {
    70  				_ = m.AddOrUpdate(k, k)
    71  			}
    72  			var ok bool
    73  			for i := 0; i < b.N; i++ {
    74  				_, ok = m.Get(keys[uint32(i)&mod])
    75  				require.True(b, ok)
    76  			}
    77  			bb.ReportAllocs()
    78  		})
    79  		b.Run("SyncMap n="+strconv.Itoa(n), func(bb *testing.B) {
    80  			keys := genStrKeys(strKeyLen, n)
    81  			n := uint32(len(keys))
    82  			mod := n - 1 // power of 2 fast modulus
    83  			require.Equal(bb, 1, bits.OnesCount32(n))
    84  			m := sync.Map{}
    85  			bb.ResetTimer()
    86  			for _, k := range keys {
    87  				m.Store(k, k)
    88  			}
    89  			var ok bool
    90  			for i := 0; i < b.N; i++ {
    91  				_, ok = m.Load(keys[uint32(i)&mod])
    92  				require.True(b, ok)
    93  			}
    94  			bb.ReportAllocs()
    95  		})
    96  	}
    97  }
    98  
    99  func BenchmarkThreadSafeMapReadWrite(b *testing.B) {
   100  	value := []byte(`abc`)
   101  	for i := 0; i <= 10; i++ {
   102  		b.Run(fmt.Sprintf("ThreadSafeMap frac_%d", i), func(bb *testing.B) {
   103  			readFrac := float32(i) / 10.0
   104  			tsm := NewThreadSafeMap[int, []byte](
   105  				WithThreadSafeMapInitCap[int, []byte](4096),
   106  			)
   107  			bb.ResetTimer()
   108  			count := atomic.Int32{}
   109  			bb.RunParallel(func(pb *testing.PB) {
   110  				for pb.Next() {
   111  					if randv2.Float32() < readFrac {
   112  						v, exists := tsm.Get(randv2.Int())
   113  						if exists && v != nil {
   114  							count.Add(1)
   115  						}
   116  					} else {
   117  						err := tsm.AddOrUpdate(randv2.Int(), value)
   118  						require.NoError(bb, err)
   119  					}
   120  				}
   121  			})
   122  		})
   123  		b.Run(fmt.Sprintf("SyncMap frac_%d", i), func(bb *testing.B) {
   124  			readFrac := float32(i) / 10.0
   125  			tsm := sync.Map{}
   126  			bb.ResetTimer()
   127  			count := atomic.Int32{}
   128  			bb.RunParallel(func(pb *testing.PB) {
   129  				for pb.Next() {
   130  					if randv2.Float32() < readFrac {
   131  						v, exists := tsm.Load(randv2.Int())
   132  						if exists && v != nil {
   133  							count.Add(1)
   134  						}
   135  					} else {
   136  						tsm.Store(randv2.Int(), value)
   137  					}
   138  				}
   139  			})
   140  		})
   141  	}
   142  }
   143  
   144  func BenchmarkStringThreadSafeMap_DataRace(b *testing.B) {
   145  	const strKeyLen = 8
   146  	sizes := []int{16, 128, 512, 1024, 8192, 131072, 1 << 19}
   147  	for _, n := range sizes {
   148  		b.Run("ThreadSafeMap n="+strconv.Itoa(n), func(bb *testing.B) {
   149  			keys := genStrKeys(strKeyLen, n)
   150  			n := uint32(len(keys))
   151  			mod := n - 1 // power of 2 fast modulus
   152  			require.Equal(bb, 1, bits.OnesCount32(n))
   153  			m := NewThreadSafeMap[string, string](WithThreadSafeMapInitCap[string, string](n))
   154  			channels := make([]chan string, 4)
   155  			for i := 0; i < len(channels); i++ {
   156  				channels[i] = make(chan string)
   157  			}
   158  			wg := sync.WaitGroup{}
   159  			wg.Add(4)
   160  			bb.ResetTimer()
   161  			go func() {
   162  				for k := range channels[0] {
   163  					_ = m.AddOrUpdate(k, k)
   164  				}
   165  				wg.Done()
   166  			}()
   167  			go func() {
   168  				for k := range channels[1] {
   169  					_ = m.AddOrUpdate(k, k)
   170  				}
   171  				wg.Done()
   172  			}()
   173  			go func() {
   174  				for k := range channels[2] {
   175  					_ = m.AddOrUpdate(k, k)
   176  				}
   177  				wg.Done()
   178  			}()
   179  			go func() {
   180  				for k := range channels[3] {
   181  					_ = m.AddOrUpdate(k, k)
   182  				}
   183  				wg.Done()
   184  			}()
   185  			for i, k := range keys {
   186  				channels[i%4] <- k
   187  			}
   188  			for i := 0; i < 4; i++ {
   189  				close(channels[i])
   190  			}
   191  			wg.Wait()
   192  			var ok bool
   193  			for i := 0; i < b.N; i++ {
   194  				_, ok = m.Get(keys[uint32(i)&mod])
   195  				require.True(b, ok)
   196  			}
   197  			bb.ReportAllocs()
   198  		})
   199  		b.Run("syncMap n="+strconv.Itoa(n), func(bb *testing.B) {
   200  			keys := genStrKeys(strKeyLen, n)
   201  			n := uint32(len(keys))
   202  			mod := n - 1 // power of 2 fast modulus
   203  			require.Equal(bb, 1, bits.OnesCount32(n))
   204  			m := sync.Map{}
   205  			channels := make([]chan string, 4)
   206  			for i := 0; i < len(channels); i++ {
   207  				channels[i] = make(chan string)
   208  			}
   209  			wg := sync.WaitGroup{}
   210  			wg.Add(4)
   211  			bb.ResetTimer()
   212  			go func() {
   213  				for k := range channels[0] {
   214  					m.Store(k, k)
   215  				}
   216  				wg.Done()
   217  			}()
   218  			go func() {
   219  				for k := range channels[1] {
   220  					m.Store(k, k)
   221  				}
   222  				wg.Done()
   223  			}()
   224  			go func() {
   225  				for k := range channels[2] {
   226  					m.Store(k, k)
   227  				}
   228  				wg.Done()
   229  			}()
   230  			go func() {
   231  				for k := range channels[3] {
   232  					m.Store(k, k)
   233  				}
   234  				wg.Done()
   235  			}()
   236  			for i, k := range keys {
   237  				channels[i%4] <- k
   238  			}
   239  			for i := 0; i < 4; i++ {
   240  				close(channels[i])
   241  			}
   242  			wg.Wait()
   243  			var ok bool
   244  			for i := 0; i < b.N; i++ {
   245  				_, ok = m.Load(keys[uint32(i)&mod])
   246  				require.True(b, ok)
   247  			}
   248  			bb.ReportAllocs()
   249  		})
   250  	}
   251  }