github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/internal/cache/robin_hood_test.go (about)

     1  // Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package cache
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"runtime"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/cockroachdb/pebble/internal/base"
    15  	"golang.org/x/exp/rand"
    16  )
    17  
    18  func TestRobinHoodMap(t *testing.T) {
    19  	rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
    20  	rhMap := newRobinHoodMap(0)
    21  	defer rhMap.free()
    22  
    23  	goMap := make(map[key]*entry)
    24  
    25  	randomKey := func() key {
    26  		n := rng.Intn(len(goMap))
    27  		for k := range goMap {
    28  			if n == 0 {
    29  				return k
    30  			}
    31  			n--
    32  		}
    33  		return key{}
    34  	}
    35  
    36  	ops := 10000 + rng.Intn(10000)
    37  	for i := 0; i < ops; i++ {
    38  		var which float64
    39  		if len(goMap) > 0 {
    40  			which = rng.Float64()
    41  		}
    42  
    43  		switch {
    44  		case which < 0.4:
    45  			// 40% insert.
    46  			var k key
    47  			k.id = rng.Uint64()
    48  			k.fileNum = base.FileNum(rng.Uint64()).DiskFileNum()
    49  			k.offset = rng.Uint64()
    50  			e := &entry{}
    51  			goMap[k] = e
    52  			rhMap.Put(k, e)
    53  			if len(goMap) != rhMap.Count() {
    54  				t.Fatalf("map sizes differ: %d != %d", len(goMap), rhMap.Count())
    55  			}
    56  
    57  		case which < 0.1:
    58  			// 10% overwrite.
    59  			k := randomKey()
    60  			e := &entry{}
    61  			goMap[k] = e
    62  			rhMap.Put(k, e)
    63  			if len(goMap) != rhMap.Count() {
    64  				t.Fatalf("map sizes differ: %d != %d", len(goMap), rhMap.Count())
    65  			}
    66  
    67  		case which < 0.75:
    68  			// 25% delete.
    69  			k := randomKey()
    70  			delete(goMap, k)
    71  			rhMap.Delete(k)
    72  			if len(goMap) != rhMap.Count() {
    73  				t.Fatalf("map sizes differ: %d != %d", len(goMap), rhMap.Count())
    74  			}
    75  
    76  		default:
    77  			// 25% lookup.
    78  			k := randomKey()
    79  			v := goMap[k]
    80  			u := rhMap.Get(k)
    81  			if v != u {
    82  				t.Fatalf("%s: expected %p, but found %p", k, v, u)
    83  			}
    84  		}
    85  	}
    86  
    87  	t.Logf("map size: %d", len(goMap))
    88  }
    89  
    90  const benchSize = 1 << 20
    91  
    92  func BenchmarkGoMapInsert(b *testing.B) {
    93  	rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
    94  	keys := make([]key, benchSize)
    95  	for i := range keys {
    96  		keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum()
    97  		keys[i].offset = uint64(rng.Intn(1 << 20))
    98  	}
    99  	b.ResetTimer()
   100  
   101  	var m map[key]*entry
   102  	for i, j := 0, 0; i < b.N; i, j = i+1, j+1 {
   103  		if m == nil || j == len(keys) {
   104  			b.StopTimer()
   105  			m = make(map[key]*entry, len(keys))
   106  			j = 0
   107  			b.StartTimer()
   108  		}
   109  		m[keys[j]] = nil
   110  	}
   111  }
   112  
   113  func BenchmarkRobinHoodInsert(b *testing.B) {
   114  	rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
   115  	keys := make([]key, benchSize)
   116  	for i := range keys {
   117  		keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum()
   118  		keys[i].offset = uint64(rng.Intn(1 << 20))
   119  	}
   120  	e := &entry{}
   121  	b.ResetTimer()
   122  
   123  	var m *robinHoodMap
   124  	for i, j := 0, 0; i < b.N; i, j = i+1, j+1 {
   125  		if m == nil || j == len(keys) {
   126  			b.StopTimer()
   127  			m = newRobinHoodMap(len(keys))
   128  			j = 0
   129  			b.StartTimer()
   130  		}
   131  		m.Put(keys[j], e)
   132  	}
   133  
   134  	runtime.KeepAlive(e)
   135  }
   136  
   137  func BenchmarkGoMapLookupHit(b *testing.B) {
   138  	rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
   139  	keys := make([]key, benchSize)
   140  	m := make(map[key]*entry, len(keys))
   141  	e := &entry{}
   142  	for i := range keys {
   143  		keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum()
   144  		keys[i].offset = uint64(rng.Intn(1 << 20))
   145  		m[keys[i]] = e
   146  	}
   147  	b.ResetTimer()
   148  
   149  	var p *entry
   150  	for i, j := 0, 0; i < b.N; i, j = i+1, j+1 {
   151  		if j == len(keys) {
   152  			j = 0
   153  		}
   154  		p = m[keys[j]]
   155  	}
   156  
   157  	if testing.Verbose() {
   158  		fmt.Fprintln(io.Discard, p)
   159  	}
   160  }
   161  
   162  func BenchmarkRobinHoodLookupHit(b *testing.B) {
   163  	rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
   164  	keys := make([]key, benchSize)
   165  	m := newRobinHoodMap(len(keys))
   166  	e := &entry{}
   167  	for i := range keys {
   168  		keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum()
   169  		keys[i].offset = uint64(rng.Intn(1 << 20))
   170  		m.Put(keys[i], e)
   171  	}
   172  	b.ResetTimer()
   173  
   174  	var p *entry
   175  	for i, j := 0, 0; i < b.N; i, j = i+1, j+1 {
   176  		if j == len(keys) {
   177  			j = 0
   178  		}
   179  		p = m.Get(keys[j])
   180  	}
   181  
   182  	if testing.Verbose() {
   183  		fmt.Fprintln(io.Discard, p)
   184  	}
   185  	runtime.KeepAlive(e)
   186  }
   187  
   188  func BenchmarkGoMapLookupMiss(b *testing.B) {
   189  	rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
   190  	keys := make([]key, benchSize)
   191  	m := make(map[key]*entry, len(keys))
   192  	e := &entry{}
   193  	for i := range keys {
   194  		keys[i].id = 1
   195  		keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum()
   196  		keys[i].offset = uint64(rng.Intn(1 << 20))
   197  		m[keys[i]] = e
   198  		keys[i].id = 2
   199  	}
   200  	b.ResetTimer()
   201  
   202  	var p *entry
   203  	for i, j := 0, 0; i < b.N; i, j = i+1, j+1 {
   204  		if j == len(keys) {
   205  			j = 0
   206  		}
   207  		p = m[keys[j]]
   208  	}
   209  
   210  	if testing.Verbose() {
   211  		fmt.Fprintln(io.Discard, p)
   212  	}
   213  }
   214  
   215  func BenchmarkRobinHoodLookupMiss(b *testing.B) {
   216  	rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
   217  	keys := make([]key, benchSize)
   218  	m := newRobinHoodMap(len(keys))
   219  	e := &entry{}
   220  	for i := range keys {
   221  		keys[i].id = 1
   222  		keys[i].fileNum = base.FileNum(rng.Uint64n(1 << 20)).DiskFileNum()
   223  		keys[i].offset = uint64(rng.Intn(1 << 20))
   224  		m.Put(keys[i], e)
   225  		keys[i].id = 2
   226  	}
   227  	b.ResetTimer()
   228  
   229  	var p *entry
   230  	for i, j := 0, 0; i < b.N; i, j = i+1, j+1 {
   231  		if j == len(keys) {
   232  			j = 0
   233  		}
   234  		p = m.Get(keys[j])
   235  	}
   236  
   237  	if testing.Verbose() {
   238  		fmt.Fprintln(io.Discard, p)
   239  	}
   240  	runtime.KeepAlive(e)
   241  }