github.com/nats-io/nats-server/v2@v2.11.0-preview.2/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 ...any) { 197 if *printResults { 198 fmt.Printf(format, args...) 199 } 200 }