mirror of https://github.com/tidwall/tile38.git
325 lines
6.9 KiB
Go
325 lines
6.9 KiB
Go
// Copyright 2019 Joshua J Baker. All rights reserved.
|
|
// Use of this source code is governed by an ISC-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package rhh
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/tidwall/lotsa"
|
|
)
|
|
|
|
type keyT = string
|
|
type valueT = interface{}
|
|
|
|
func k(key int) keyT {
|
|
return strconv.FormatInt(int64(key), 10)
|
|
}
|
|
|
|
func add(x keyT, delta int) int {
|
|
i, err := strconv.ParseInt(x, 10, 64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return int(i + int64(delta))
|
|
}
|
|
|
|
///////////////////////////
|
|
func random(N int, perm bool) []keyT {
|
|
nums := make([]keyT, N)
|
|
if perm {
|
|
for i, x := range rand.Perm(N) {
|
|
nums[i] = k(x)
|
|
}
|
|
} else {
|
|
m := make(map[keyT]bool)
|
|
for len(m) < N {
|
|
m[k(int(rand.Uint64()))] = true
|
|
}
|
|
var i int
|
|
for k := range m {
|
|
nums[i] = k
|
|
i++
|
|
}
|
|
}
|
|
return nums
|
|
}
|
|
|
|
func shuffle(nums []keyT) {
|
|
for i := range nums {
|
|
j := rand.Intn(i + 1)
|
|
nums[i], nums[j] = nums[j], nums[i]
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
//var seed int64 = 1519776033517775607
|
|
seed := (time.Now().UnixNano())
|
|
println("seed:", seed)
|
|
rand.Seed(seed)
|
|
}
|
|
|
|
func TestRandomData(t *testing.T) {
|
|
N := 10000
|
|
start := time.Now()
|
|
for time.Since(start) < time.Second*2 {
|
|
nums := random(N, true)
|
|
var m *Map
|
|
switch rand.Int() % 5 {
|
|
default:
|
|
m = New(N / ((rand.Int() % 3) + 1))
|
|
case 1:
|
|
m = new(Map)
|
|
case 2:
|
|
m = New(0)
|
|
}
|
|
v, ok := m.Get(k(999))
|
|
if ok || v != nil {
|
|
t.Fatalf("expected %v, got %v", nil, v)
|
|
}
|
|
v, ok = m.Delete(k(999))
|
|
if ok || v != nil {
|
|
t.Fatalf("expected %v, got %v", nil, v)
|
|
}
|
|
if m.Len() != 0 {
|
|
t.Fatalf("expected %v, got %v", 0, m.Len())
|
|
}
|
|
// set a bunch of items
|
|
for i := 0; i < len(nums); i++ {
|
|
v, ok := m.Set(nums[i], nums[i])
|
|
if ok || v != nil {
|
|
t.Fatalf("expected %v, got %v", nil, v)
|
|
}
|
|
}
|
|
if m.Len() != N {
|
|
t.Fatalf("expected %v, got %v", N, m.Len())
|
|
}
|
|
// retrieve all the items
|
|
shuffle(nums)
|
|
for i := 0; i < len(nums); i++ {
|
|
v, ok := m.Get(nums[i])
|
|
if !ok || v == nil || v != nums[i] {
|
|
t.Fatalf("expected %v, got %v", nums[i], v)
|
|
}
|
|
}
|
|
// replace all the items
|
|
shuffle(nums)
|
|
for i := 0; i < len(nums); i++ {
|
|
v, ok := m.Set(nums[i], add(nums[i], 1))
|
|
if !ok || v != nums[i] {
|
|
t.Fatalf("expected %v, got %v", nums[i], v)
|
|
}
|
|
}
|
|
if m.Len() != N {
|
|
t.Fatalf("expected %v, got %v", N, m.Len())
|
|
}
|
|
// retrieve all the items
|
|
shuffle(nums)
|
|
for i := 0; i < len(nums); i++ {
|
|
v, ok := m.Get(nums[i])
|
|
if !ok || v != add(nums[i], 1) {
|
|
t.Fatalf("expected %v, got %v", add(nums[i], 1), v)
|
|
}
|
|
}
|
|
// remove half the items
|
|
shuffle(nums)
|
|
for i := 0; i < len(nums)/2; i++ {
|
|
v, ok := m.Delete(nums[i])
|
|
if !ok || v != add(nums[i], 1) {
|
|
t.Fatalf("expected %v, got %v", add(nums[i], 1), v)
|
|
}
|
|
}
|
|
if m.Len() != N/2 {
|
|
t.Fatalf("expected %v, got %v", N/2, m.Len())
|
|
}
|
|
// check to make sure that the items have been removed
|
|
for i := 0; i < len(nums)/2; i++ {
|
|
v, ok := m.Get(nums[i])
|
|
if ok || v != nil {
|
|
t.Fatalf("expected %v, got %v", nil, v)
|
|
}
|
|
}
|
|
// check the second half of the items
|
|
for i := len(nums) / 2; i < len(nums); i++ {
|
|
v, ok := m.Get(nums[i])
|
|
if !ok || v != add(nums[i], 1) {
|
|
t.Fatalf("expected %v, got %v", add(nums[i], 1), v)
|
|
}
|
|
}
|
|
// try to delete again, make sure they don't exist
|
|
for i := 0; i < len(nums)/2; i++ {
|
|
v, ok := m.Delete(nums[i])
|
|
if ok || v != nil {
|
|
t.Fatalf("expected %v, got %v", nil, v)
|
|
}
|
|
}
|
|
if m.Len() != N/2 {
|
|
t.Fatalf("expected %v, got %v", N/2, m.Len())
|
|
}
|
|
m.Range(func(key keyT, value valueT) bool {
|
|
if value != add(key, 1) {
|
|
t.Fatalf("expected %v, got %v", add(key, 1), value)
|
|
}
|
|
return true
|
|
})
|
|
var n int
|
|
m.Range(func(key keyT, value valueT) bool {
|
|
n++
|
|
return false
|
|
})
|
|
if n != 1 {
|
|
t.Fatalf("expected %v, got %v", 1, n)
|
|
}
|
|
for i := len(nums) / 2; i < len(nums); i++ {
|
|
v, ok := m.Delete(nums[i])
|
|
if !ok || v != add(nums[i], 1) {
|
|
t.Fatalf("expected %v, got %v", add(nums[i], 1), v)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBench(t *testing.T) {
|
|
N, _ := strconv.ParseUint(os.Getenv("MAPBENCH"), 10, 64)
|
|
if N == 0 {
|
|
fmt.Printf("Enable benchmarks with MAPBENCH=1000000\n")
|
|
return
|
|
}
|
|
nums := random(int(N), false)
|
|
var pnums []valueT
|
|
for i := range nums {
|
|
pnums = append(pnums, valueT(&nums[i]))
|
|
}
|
|
fmt.Printf("\n## STRING KEYS\n\n")
|
|
t.Run("RobinHood", func(t *testing.T) {
|
|
testPerf(nums, pnums, "robinhood")
|
|
})
|
|
t.Run("Stdlib", func(t *testing.T) {
|
|
testPerf(nums, pnums, "stdlib")
|
|
})
|
|
}
|
|
|
|
func printItem(s string, size int, dir int) {
|
|
for len(s) < size {
|
|
if dir == -1 {
|
|
s += " "
|
|
} else {
|
|
s = " " + s
|
|
}
|
|
}
|
|
fmt.Printf("%s ", s)
|
|
}
|
|
|
|
func testPerf(nums []keyT, pnums []valueT, which keyT) {
|
|
var ms1, ms2 runtime.MemStats
|
|
initSize := 0 //len(nums) * 2
|
|
defer func() {
|
|
heapBytes := int(ms2.HeapAlloc - ms1.HeapAlloc)
|
|
fmt.Printf("memory %13s bytes %19s/entry \n",
|
|
commaize(heapBytes), commaize(heapBytes/len(nums)))
|
|
fmt.Printf("\n")
|
|
}()
|
|
runtime.GC()
|
|
time.Sleep(time.Millisecond * 100)
|
|
runtime.ReadMemStats(&ms1)
|
|
|
|
var setop, getop, delop func(int, int)
|
|
var scnop func()
|
|
switch which {
|
|
case "stdlib":
|
|
m := make(map[keyT]valueT, initSize)
|
|
setop = func(i, _ int) { m[nums[i]] = pnums[i] }
|
|
getop = func(i, _ int) { _ = m[nums[i]] }
|
|
delop = func(i, _ int) { delete(m, nums[i]) }
|
|
scnop = func() {
|
|
for range m {
|
|
}
|
|
}
|
|
case "robinhood":
|
|
m := New(initSize)
|
|
setop = func(i, _ int) { m.Set(nums[i], pnums[i]) }
|
|
getop = func(i, _ int) { m.Get(nums[i]) }
|
|
delop = func(i, _ int) { m.Delete(nums[i]) }
|
|
scnop = func() {
|
|
m.Range(func(key keyT, value valueT) bool {
|
|
return true
|
|
})
|
|
}
|
|
}
|
|
fmt.Printf("-- %s --", which)
|
|
fmt.Printf("\n")
|
|
|
|
ops := []func(int, int){setop, getop, setop, nil, delop}
|
|
tags := []keyT{"set", "get", "reset", "scan", "delete"}
|
|
for i := range ops {
|
|
shuffle(nums)
|
|
var na bool
|
|
var n int
|
|
start := time.Now()
|
|
if tags[i] == "scan" {
|
|
op := scnop
|
|
if op == nil {
|
|
na = true
|
|
} else {
|
|
n = 20
|
|
lotsa.Ops(n, 1, func(_, _ int) { op() })
|
|
}
|
|
|
|
} else {
|
|
n = len(nums)
|
|
lotsa.Ops(n, 1, ops[i])
|
|
}
|
|
dur := time.Since(start)
|
|
if i == 0 {
|
|
runtime.GC()
|
|
time.Sleep(time.Millisecond * 100)
|
|
runtime.ReadMemStats(&ms2)
|
|
}
|
|
printItem(tags[i], 9, -1)
|
|
if na {
|
|
printItem("-- unavailable --", 14, 1)
|
|
} else {
|
|
if n == -1 {
|
|
printItem("unknown ops", 14, 1)
|
|
} else {
|
|
printItem(fmt.Sprintf("%s ops", commaize(n)), 14, 1)
|
|
}
|
|
printItem(fmt.Sprintf("%.0fms", dur.Seconds()*1000), 8, 1)
|
|
if n != -1 {
|
|
printItem(fmt.Sprintf("%s/sec", commaize(int(float64(n)/dur.Seconds()))), 18, 1)
|
|
}
|
|
}
|
|
fmt.Printf("\n")
|
|
}
|
|
}
|
|
|
|
func commaize(n int) string {
|
|
s1, s2 := fmt.Sprintf("%d", n), ""
|
|
for i, j := len(s1)-1, 0; i >= 0; i, j = i-1, j+1 {
|
|
if j%3 == 0 && j != 0 {
|
|
s2 = "," + s2
|
|
}
|
|
s2 = string(s1[i]) + s2
|
|
}
|
|
return s2
|
|
}
|
|
|
|
func TestHashDIB(t *testing.T) {
|
|
var e entry
|
|
e.setDIB(100)
|
|
e.setHash(90000)
|
|
if e.dib() != 100 {
|
|
t.Fatalf("expected %v, got %v", 100, e.dib())
|
|
}
|
|
if e.hash() != 90000 {
|
|
t.Fatalf("expected %v, got %v", 90000, e.hash())
|
|
}
|
|
}
|