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