get.pme.sh/pnats@v0.0.0-20240304004023-26bb5a137ed0/server/avl/norace_test.go (about)

     1  // Copyright 2023 The NATS Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  //go:build !race && !skip_no_race_tests
    15  // +build !race,!skip_no_race_tests
    16  
    17  package avl
    18  
    19  import (
    20  	"flag"
    21  	"fmt"
    22  	"math"
    23  	"math/rand"
    24  	"runtime"
    25  	"runtime/debug"
    26  	"testing"
    27  	"time"
    28  )
    29  
    30  // Print Results: go test -v  --args --results
    31  var printResults = flag.Bool("results", false, "Enable Results Logging")
    32  
    33  // SequenceSet memory tests vs dmaps.
    34  func TestNoRaceSeqSetSizeComparison(t *testing.T) {
    35  	// Create 5M random entries (dupes possible but ok for this test) out of 8M range.
    36  	num := 5_000_000
    37  	max := 7_000_000
    38  
    39  	seqs := make([]uint64, 0, num)
    40  	for i := 0; i < num; i++ {
    41  		n := uint64(rand.Int63n(int64(max + 1)))
    42  		seqs = append(seqs, n)
    43  	}
    44  
    45  	runtime.GC()
    46  	// Disable to get stable results.
    47  	gcp := debug.SetGCPercent(-1)
    48  	defer debug.SetGCPercent(gcp)
    49  
    50  	mem := runtime.MemStats{}
    51  	runtime.ReadMemStats(&mem)
    52  	inUseBefore := mem.HeapInuse
    53  
    54  	dmap := make(map[uint64]struct{}, num)
    55  	for _, n := range seqs {
    56  		dmap[n] = struct{}{}
    57  	}
    58  	runtime.ReadMemStats(&mem)
    59  	dmapUse := mem.HeapInuse - inUseBefore
    60  	inUseBefore = mem.HeapInuse
    61  
    62  	// Now do SequenceSet on same dataset.
    63  	var sset SequenceSet
    64  	for _, n := range seqs {
    65  		sset.Insert(n)
    66  	}
    67  
    68  	runtime.ReadMemStats(&mem)
    69  	seqSetUse := mem.HeapInuse - inUseBefore
    70  
    71  	if seqSetUse > 2*1024*1024 {
    72  		t.Fatalf("Expected SequenceSet size to be < 2M, got %v", friendlyBytes(seqSetUse))
    73  	}
    74  	if seqSetUse*50 > dmapUse {
    75  		t.Fatalf("Expected SequenceSet to be at least 50x better then dmap approach: %v vs %v",
    76  			friendlyBytes(seqSetUse),
    77  			friendlyBytes(dmapUse),
    78  		)
    79  	}
    80  }
    81  
    82  func TestNoRaceSeqSetEncodeLarge(t *testing.T) {
    83  	num := 2_500_000
    84  	max := 5_000_000
    85  
    86  	dmap := make(map[uint64]struct{}, num)
    87  	var ss SequenceSet
    88  	for i := 0; i < num; i++ {
    89  		n := uint64(rand.Int63n(int64(max + 1)))
    90  		ss.Insert(n)
    91  		dmap[n] = struct{}{}
    92  	}
    93  
    94  	// Disable to get stable results.
    95  	gcp := debug.SetGCPercent(-1)
    96  	defer debug.SetGCPercent(gcp)
    97  
    98  	// In general should be about the same, but can see some variability.
    99  	expected := time.Millisecond
   100  
   101  	start := time.Now()
   102  	b, err := ss.Encode(nil)
   103  	require_NoError(t, err)
   104  
   105  	if elapsed := time.Since(start); elapsed > expected {
   106  		t.Fatalf("Expected encode of %d items with encoded size %v to take less than %v, got %v",
   107  			num, friendlyBytes(len(b)), expected, elapsed)
   108  	} else {
   109  		logResults("Encode time for %d items was %v, encoded size is %v\n", num, elapsed, friendlyBytes(len(b)))
   110  	}
   111  
   112  	start = time.Now()
   113  	ss2, _, err := Decode(b)
   114  	require_NoError(t, err)
   115  	if elapsed := time.Since(start); elapsed > expected {
   116  		t.Fatalf("Expected decode to take less than %v, got %v", expected, elapsed)
   117  	} else {
   118  		logResults("Decode time is %v\n", elapsed)
   119  	}
   120  	require_True(t, ss.Nodes() == ss2.Nodes())
   121  	require_True(t, ss.Size() == ss2.Size())
   122  }
   123  
   124  func TestNoRaceSeqSetRelativeSpeed(t *testing.T) {
   125  	// Create 1M random entries (dupes possible but ok for this test) out of 3M range.
   126  	num := 1_000_000
   127  	max := 3_000_000
   128  
   129  	seqs := make([]uint64, 0, num)
   130  	for i := 0; i < num; i++ {
   131  		n := uint64(rand.Int63n(int64(max + 1)))
   132  		seqs = append(seqs, n)
   133  	}
   134  
   135  	start := time.Now()
   136  	// Now do SequenceSet on same dataset.
   137  	var sset SequenceSet
   138  	for _, n := range seqs {
   139  		sset.Insert(n)
   140  	}
   141  	ssInsertElapsed := time.Since(start)
   142  	logResults("Inserts SequenceSet: %v for %d items\n", ssInsertElapsed, num)
   143  
   144  	start = time.Now()
   145  	for _, n := range seqs {
   146  		if ok := sset.Exists(n); !ok {
   147  			t.Fatalf("Should exist")
   148  		}
   149  	}
   150  	ssLookupElapsed := time.Since(start)
   151  	logResults("Lookups: %v\n", ssLookupElapsed)
   152  
   153  	// Now do a map.
   154  	dmap := make(map[uint64]struct{})
   155  	start = time.Now()
   156  	for _, n := range seqs {
   157  		dmap[n] = struct{}{}
   158  	}
   159  	mapInsertElapsed := time.Since(start)
   160  	logResults("Inserts Map[uint64]: %v for %d items\n", mapInsertElapsed, num)
   161  
   162  	start = time.Now()
   163  	for _, n := range seqs {
   164  		if _, ok := dmap[n]; !ok {
   165  			t.Fatalf("Should exist")
   166  		}
   167  	}
   168  	mapLookupElapsed := time.Since(start)
   169  	logResults("Lookups: %v\n", mapLookupElapsed)
   170  
   171  	// In general we are between 1.5 and 1.75 times slower atm then a straight map.
   172  	// Let's test an upper bound of 2x for now.
   173  	if mapInsertElapsed*2 <= ssInsertElapsed {
   174  		t.Fatalf("Expected SequenceSet insert to be no more than 2x slower (%v vs %v)", mapInsertElapsed, ssInsertElapsed)
   175  	}
   176  
   177  	if mapLookupElapsed*3 <= ssLookupElapsed {
   178  		t.Fatalf("Expected SequenceSet lookups to be no more than 3x slower (%v vs %v)", mapLookupElapsed, ssLookupElapsed)
   179  	}
   180  }
   181  
   182  // friendlyBytes returns a string with the given bytes int64
   183  // represented as a size, such as 1KB, 10MB, etc...
   184  func friendlyBytes[T int | uint64 | int64](bytes T) string {
   185  	fbytes := float64(bytes)
   186  	base := 1024
   187  	pre := []string{"K", "M", "G", "T", "P", "E"}
   188  	if fbytes < float64(base) {
   189  		return fmt.Sprintf("%v B", fbytes)
   190  	}
   191  	exp := int(math.Log(fbytes) / math.Log(float64(base)))
   192  	index := exp - 1
   193  	return fmt.Sprintf("%.2f %sB", fbytes/math.Pow(float64(base), float64(exp)), pre[index])
   194  }
   195  
   196  func logResults(format string, args ...interface{}) {
   197  	if *printResults {
   198  		fmt.Printf(format, args...)
   199  	}
   200  }