blob: 3649f144495cbb4707114082ad35ea291f7ffc4a [file] [log] [blame]
// Copyright 2017 The Bazel Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package starlark
import (
"fmt"
"math/rand"
"sync"
"testing"
)
func TestHashtable(t *testing.T) {
makeTestIntsOnce.Do(makeTestInts)
testHashtable(t, make(map[int]bool))
}
func BenchmarkStringHash(b *testing.B) {
for len := 1; len <= 1024; len *= 2 {
buf := make([]byte, len)
rand.New(rand.NewSource(0)).Read(buf)
s := string(buf)
b.Run(fmt.Sprintf("hard-%d", len), func(b *testing.B) {
for i := 0; i < b.N; i++ {
hashString(s)
}
})
b.Run(fmt.Sprintf("soft-%d", len), func(b *testing.B) {
for i := 0; i < b.N; i++ {
softHashString(s)
}
})
}
}
func BenchmarkHashtable(b *testing.B) {
makeTestIntsOnce.Do(makeTestInts)
b.ResetTimer()
for i := 0; i < b.N; i++ {
testHashtable(b, nil)
}
}
const testIters = 10000
var (
// testInts is a zipf-distributed array of Ints and corresponding ints.
// This removes the cost of generating them on the fly during benchmarking.
// Without this, Zipf and MakeInt dominate CPU and memory costs, respectively.
makeTestIntsOnce sync.Once
testInts [3 * testIters]struct {
Int Int
goInt int
}
)
func makeTestInts() {
zipf := rand.NewZipf(rand.New(rand.NewSource(0)), 1.1, 1.0, 1000.0)
for i := range &testInts {
r := int(zipf.Uint64())
testInts[i].goInt = r
testInts[i].Int = MakeInt(r)
}
}
// testHashtable is both a test and a benchmark of hashtable.
// When sane != nil, it acts as a test against the semantics of Go's map.
func testHashtable(tb testing.TB, sane map[int]bool) {
var i int // index into testInts
var ht hashtable
// Insert 10000 random ints into the map.
for j := 0; j < testIters; j++ {
k := testInts[i]
i++
if err := ht.insert(k.Int, None); err != nil {
tb.Fatal(err)
}
if sane != nil {
sane[k.goInt] = true
}
}
// Do 10000 random lookups in the map.
for j := 0; j < testIters; j++ {
k := testInts[i]
i++
_, found, err := ht.lookup(k.Int)
if err != nil {
tb.Fatal(err)
}
if sane != nil {
_, found2 := sane[k.goInt]
if found != found2 {
tb.Fatal("sanity check failed")
}
}
}
// Do 10000 random deletes from the map.
for j := 0; j < testIters; j++ {
k := testInts[i]
i++
_, found, err := ht.delete(k.Int)
if err != nil {
tb.Fatal(err)
}
if sane != nil {
_, found2 := sane[k.goInt]
if found != found2 {
tb.Fatal("sanity check failed")
}
delete(sane, k.goInt)
}
}
if sane != nil {
if int(ht.len) != len(sane) {
tb.Fatal("sanity check failed")
}
}
}