go.starlark.net@v0.0.0-20231101134539-556fd59b42f6/starlark/hashtable_test.go (about) 1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package starlark 6 7 import ( 8 "fmt" 9 "math/rand" 10 "sync" 11 "testing" 12 ) 13 14 func TestHashtable(t *testing.T) { 15 makeTestIntsOnce.Do(makeTestInts) 16 testHashtable(t, make(map[int]bool)) 17 } 18 19 func BenchmarkStringHash(b *testing.B) { 20 for len := 1; len <= 1024; len *= 2 { 21 buf := make([]byte, len) 22 rand.New(rand.NewSource(0)).Read(buf) 23 s := string(buf) 24 25 b.Run(fmt.Sprintf("hard-%d", len), func(b *testing.B) { 26 for i := 0; i < b.N; i++ { 27 hashString(s) 28 } 29 }) 30 b.Run(fmt.Sprintf("soft-%d", len), func(b *testing.B) { 31 for i := 0; i < b.N; i++ { 32 softHashString(s) 33 } 34 }) 35 } 36 } 37 38 func BenchmarkHashtable(b *testing.B) { 39 makeTestIntsOnce.Do(makeTestInts) 40 b.ResetTimer() 41 for i := 0; i < b.N; i++ { 42 testHashtable(b, nil) 43 } 44 } 45 46 const testIters = 10000 47 48 var ( 49 // testInts is a zipf-distributed array of Ints and corresponding ints. 50 // This removes the cost of generating them on the fly during benchmarking. 51 // Without this, Zipf and MakeInt dominate CPU and memory costs, respectively. 52 makeTestIntsOnce sync.Once 53 testInts [3 * testIters]struct { 54 Int Int 55 goInt int 56 } 57 ) 58 59 func makeTestInts() { 60 zipf := rand.NewZipf(rand.New(rand.NewSource(0)), 1.1, 1.0, 1000.0) 61 for i := range &testInts { 62 r := int(zipf.Uint64()) 63 testInts[i].goInt = r 64 testInts[i].Int = MakeInt(r) 65 } 66 } 67 68 // testHashtable is both a test and a benchmark of hashtable. 69 // When sane != nil, it acts as a test against the semantics of Go's map. 70 func testHashtable(tb testing.TB, sane map[int]bool) { 71 var i int // index into testInts 72 73 var ht hashtable 74 75 // Insert 10000 random ints into the map. 76 for j := 0; j < testIters; j++ { 77 k := testInts[i] 78 i++ 79 if err := ht.insert(k.Int, None); err != nil { 80 tb.Fatal(err) 81 } 82 if sane != nil { 83 sane[k.goInt] = true 84 } 85 } 86 87 // Do 10000 random lookups in the map. 88 for j := 0; j < testIters; j++ { 89 k := testInts[i] 90 i++ 91 _, found, err := ht.lookup(k.Int) 92 if err != nil { 93 tb.Fatal(err) 94 } 95 if sane != nil { 96 _, found2 := sane[k.goInt] 97 if found != found2 { 98 tb.Fatal("sanity check failed") 99 } 100 } 101 } 102 103 // Do 10000 random deletes from the map. 104 for j := 0; j < testIters; j++ { 105 k := testInts[i] 106 i++ 107 _, found, err := ht.delete(k.Int) 108 if err != nil { 109 tb.Fatal(err) 110 } 111 if sane != nil { 112 _, found2 := sane[k.goInt] 113 if found != found2 { 114 tb.Fatal("sanity check failed") 115 } 116 delete(sane, k.goInt) 117 } 118 } 119 120 if sane != nil { 121 if int(ht.len) != len(sane) { 122 tb.Fatal("sanity check failed") 123 } 124 } 125 } 126 127 func TestHashtableCount(t *testing.T) { 128 const count = 1000 129 ht := new(hashtable) 130 for i := 0; i < count; i++ { 131 ht.insert(MakeInt(i), None) 132 } 133 134 if c, err := ht.count(rangeValue{0, count, 1, count}.Iterate()); err != nil { 135 t.Error(err) 136 } else if c != count { 137 t.Errorf("count doesn't match: expected %d got %d", count, c) 138 } 139 }