github.com/blong14/gache@v0.0.0-20240124023949-89416fd8bbfa/internal/map/tablemap/map_test.go (about)

     1  package tablemap_test
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  	"sync/atomic"
    10  	"testing"
    11  	"time"
    12  
    13  	gtable "github.com/blong14/gache/internal/map/tablemap"
    14  )
    15  
    16  func testGetAndSet(t *testing.T) {
    17  	t.Parallel()
    18  	// given
    19  	tree := gtable.NewWithOptions[[]byte, []byte](
    20  		bytes.Compare,
    21  		gtable.WithCapacity[[]byte, []byte](1024),
    22  	)
    23  	start := time.Now()
    24  	count := 5_000
    25  	var wg sync.WaitGroup
    26  	for i := 0; i < count; i++ {
    27  		wg.Add(1)
    28  		go func(idx int) {
    29  			defer wg.Done()
    30  			key := []byte(strconv.Itoa(idx))
    31  			tree.Set(key, []byte(fmt.Sprintf("value_%d", idx)))
    32  		}(i)
    33  	}
    34  	wg.Wait()
    35  	for i := 0; i < count; i++ {
    36  		wg.Add(1)
    37  		go func(i int) {
    38  			defer wg.Done()
    39  			k := []byte(strconv.Itoa(i))
    40  			if _, ok := tree.Get(k); !ok {
    41  				t.Errorf("missing rawKey %d", i)
    42  			}
    43  		}(i)
    44  	}
    45  	wg.Wait()
    46  	t.Logf("%s", time.Since(start))
    47  }
    48  
    49  func testRange(t *testing.T) {
    50  	t.Parallel()
    51  	// given
    52  	tree := gtable.New[string, string](strings.Compare)
    53  	expected := []string{
    54  		"key8",
    55  		"key2",
    56  		"key",
    57  		"key5",
    58  		"key3",
    59  		"key10",
    60  		"key7",
    61  		"key12",
    62  		"key6",
    63  		"key9",
    64  		"key4",
    65  		"-",
    66  	}
    67  	for i, key := range expected {
    68  		tree.Set(key, fmt.Sprintf("value%d", i))
    69  	}
    70  
    71  	// when
    72  	var keys []string
    73  	tree.Range(func(k, _ string) bool {
    74  		keys = append(keys, k)
    75  		return true
    76  	})
    77  
    78  	// then
    79  	for _, key := range keys {
    80  		_, ok := tree.Get(key)
    81  		if !ok {
    82  			t.Errorf("%v not found", key)
    83  		}
    84  	}
    85  }
    86  
    87  func TestTableMap(t *testing.T) {
    88  	t.Parallel()
    89  
    90  	t.Run("get and set", testGetAndSet)
    91  	t.Run("range", testRange)
    92  }
    93  
    94  type bench struct {
    95  	setup    func(*testing.B, *gtable.TableMap[string, string])
    96  	perG     func(b *testing.B, pb *testing.PB, i int, m *gtable.TableMap[string, string])
    97  	teardown func(*testing.B, *gtable.TableMap[string, string]) func()
    98  }
    99  
   100  func newMap() *gtable.TableMap[string, string] {
   101  	return gtable.New[string, string](strings.Compare)
   102  }
   103  
   104  func benchMap(b *testing.B, bench bench) {
   105  	b.Run("tablemap benchmark", func(b *testing.B) {
   106  		m := newMap()
   107  		if bench.setup != nil {
   108  			bench.setup(b, m)
   109  		}
   110  		b.ReportAllocs()
   111  		b.ResetTimer()
   112  		var i int64
   113  		b.RunParallel(func(pb *testing.PB) {
   114  			id := int(atomic.AddInt64(&i, 1) - 1)
   115  			bench.perG(b, pb, id*b.N, m)
   116  		})
   117  		if bench.teardown != nil {
   118  			b.Cleanup(bench.teardown(b, m))
   119  		}
   120  	})
   121  }
   122  
   123  func BenchmarkConcurrent_LoadMostlyHits(b *testing.B) {
   124  	const hits, misses = 1023, 1
   125  
   126  	benchMap(b, bench{
   127  		setup: func(_ *testing.B, m *gtable.TableMap[string, string]) {
   128  			for i := 0; i < hits; i++ {
   129  				m.Set(strconv.Itoa(i), strconv.Itoa(i))
   130  			}
   131  			// Prime the map to get it into a steady state.
   132  			for i := 0; i < hits*2; i++ {
   133  				m.Range(func(_, _ string) bool { return true })
   134  			}
   135  		},
   136  		perG: func(b *testing.B, pb *testing.PB, i int, m *gtable.TableMap[string, string]) {
   137  			for ; pb.Next(); i++ {
   138  				m.Get(strconv.Itoa(i % (hits + misses)))
   139  			}
   140  		},
   141  	})
   142  
   143  }
   144  
   145  func BenchmarkConcurrent_LoadOrStoreBalanced(b *testing.B) {
   146  	const hits, misses = 1023, 1023
   147  
   148  	benchMap(b, bench{
   149  		setup: func(b *testing.B, m *gtable.TableMap[string, string]) {
   150  			for i := 0; i < hits; i++ {
   151  				m.Set(strconv.Itoa(i), strconv.Itoa(i))
   152  			}
   153  			// Prime the map to get it into a steady state.
   154  			for i := 0; i < hits*2; i++ {
   155  				m.Range(func(_, _ string) bool { return true })
   156  			}
   157  		},
   158  		perG: func(b *testing.B, pb *testing.PB, i int, m *gtable.TableMap[string, string]) {
   159  			for ; pb.Next(); i++ {
   160  				j := i % (hits + misses)
   161  				if j < hits {
   162  					if _, ok := m.Get(strconv.Itoa(j)); !ok {
   163  						b.Fatalf("unexpected miss for key %v", j)
   164  					}
   165  				} else {
   166  					m.Set(strconv.Itoa(j), strconv.Itoa(j))
   167  				}
   168  			}
   169  		},
   170  	})
   171  }
   172  
   173  func BenchmarkConcurrent_LoadOrStoreCollision(b *testing.B) {
   174  	benchMap(b, bench{
   175  		perG: func(b *testing.B, pb *testing.PB, i int, m *gtable.TableMap[string, string]) {
   176  			for ; pb.Next(); i++ {
   177  				m.Set("key", "value")
   178  			}
   179  		},
   180  	})
   181  }
   182  
   183  func BenchmarkConcurrent_Range(b *testing.B) {
   184  	const mapSize = 1 << 10
   185  
   186  	benchMap(b, bench{
   187  		setup: func(_ *testing.B, m *gtable.TableMap[string, string]) {
   188  			for i := 0; i < mapSize; i++ {
   189  				m.Set(strconv.Itoa(i), strconv.Itoa(i))
   190  			}
   191  		},
   192  		perG: func(b *testing.B, pb *testing.PB, i int, m *gtable.TableMap[string, string]) {
   193  			for ; pb.Next(); i++ {
   194  				m.Range(func(_, _ string) bool { return true })
   195  			}
   196  		},
   197  	})
   198  }