kevo/pkg/memtable/bench_test.go
Jeremy Tregunna 7e226825df
All checks were successful
Go Tests / Run Tests (1.24.2) (push) Successful in 9m48s
fix: engine refactor bugfix fest, go fmt
2025-04-25 23:36:08 -06:00

317 lines
7.6 KiB
Go

package memtable
import (
"fmt"
"math/rand"
"strconv"
"testing"
)
func BenchmarkSkipListInsert(b *testing.B) {
sl := NewSkipList()
// Create random keys ahead of time
keys := make([][]byte, b.N)
values := make([][]byte, b.N)
for i := 0; i < b.N; i++ {
keys[i] = []byte(fmt.Sprintf("key-%d", i))
values[i] = []byte(fmt.Sprintf("value-%d", i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
e := newEntry(keys[i], values[i], TypeValue, uint64(i))
sl.Insert(e)
}
}
func BenchmarkSkipListFind(b *testing.B) {
sl := NewSkipList()
// Insert entries first
const numEntries = 100000
keys := make([][]byte, numEntries)
for i := 0; i < numEntries; i++ {
key := []byte(fmt.Sprintf("key-%d", i))
value := []byte(fmt.Sprintf("value-%d", i))
keys[i] = key
sl.Insert(newEntry(key, value, TypeValue, uint64(i)))
}
// Create random keys for lookup
lookupKeys := make([][]byte, b.N)
r := rand.New(rand.NewSource(42)) // Use fixed seed for reproducibility
for i := 0; i < b.N; i++ {
idx := r.Intn(numEntries)
lookupKeys[i] = keys[idx]
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
sl.Find(lookupKeys[i])
}
}
func BenchmarkMemTablePut(b *testing.B) {
mt := NewMemTable()
b.ResetTimer()
for i := 0; i < b.N; i++ {
key := []byte("key-" + strconv.Itoa(i))
value := []byte("value-" + strconv.Itoa(i))
mt.Put(key, value, uint64(i))
}
}
func BenchmarkMemTableGet(b *testing.B) {
mt := NewMemTable()
// Insert entries first
const numEntries = 100000
keys := make([][]byte, numEntries)
for i := 0; i < numEntries; i++ {
key := []byte(fmt.Sprintf("key-%d", i))
value := []byte(fmt.Sprintf("value-%d", i))
keys[i] = key
mt.Put(key, value, uint64(i))
}
// Create random keys for lookup
lookupKeys := make([][]byte, b.N)
r := rand.New(rand.NewSource(42)) // Use fixed seed for reproducibility
for i := 0; i < b.N; i++ {
idx := r.Intn(numEntries)
lookupKeys[i] = keys[idx]
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
mt.Get(lookupKeys[i])
}
}
func BenchmarkMemTableDelete(b *testing.B) {
mt := NewMemTable()
// Prepare keys ahead of time
keys := make([][]byte, b.N)
for i := 0; i < b.N; i++ {
keys[i] = []byte(fmt.Sprintf("key-%d", i))
}
// Insert entries first
for i := 0; i < b.N; i++ {
value := []byte(fmt.Sprintf("value-%d", i))
mt.Put(keys[i], value, uint64(i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
mt.Delete(keys[i], uint64(i+b.N))
}
}
func BenchmarkImmutableMemTableGet(b *testing.B) {
mt := NewMemTable()
// Insert entries first
const numEntries = 100000
keys := make([][]byte, numEntries)
for i := 0; i < numEntries; i++ {
key := []byte(fmt.Sprintf("key-%d", i))
value := []byte(fmt.Sprintf("value-%d", i))
keys[i] = key
mt.Put(key, value, uint64(i))
}
// Mark memtable as immutable
mt.SetImmutable()
// Create random keys for lookup
lookupKeys := make([][]byte, b.N)
r := rand.New(rand.NewSource(42)) // Use fixed seed for reproducibility
for i := 0; i < b.N; i++ {
idx := r.Intn(numEntries)
lookupKeys[i] = keys[idx]
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
mt.Get(lookupKeys[i])
}
}
func BenchmarkConcurrentMemTableGet(b *testing.B) {
// This benchmark tests concurrent read performance on a mutable memtable
mt := NewMemTable()
// Insert entries first
const numEntries = 100000
keys := make([][]byte, numEntries)
for i := 0; i < numEntries; i++ {
key := []byte(fmt.Sprintf("key-%d", i))
value := []byte(fmt.Sprintf("value-%d", i))
keys[i] = key
mt.Put(key, value, uint64(i))
}
// Create random keys for lookup
r := rand.New(rand.NewSource(42)) // Use fixed seed for reproducibility
lookupKeys := make([][]byte, b.N)
for i := 0; i < b.N; i++ {
idx := r.Intn(numEntries)
lookupKeys[i] = keys[idx]
}
// Set up for parallel benchmark
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
// Each goroutine needs its own random sequence
localRand := rand.New(rand.NewSource(rand.Int63()))
i := 0
for pb.Next() {
// Pick a random key from our prepared list
idx := localRand.Intn(len(lookupKeys))
mt.Get(lookupKeys[idx])
i++
}
})
}
func BenchmarkConcurrentImmutableMemTableGet(b *testing.B) {
// This benchmark tests concurrent read performance on an immutable memtable
mt := NewMemTable()
// Insert entries first
const numEntries = 100000
keys := make([][]byte, numEntries)
for i := 0; i < numEntries; i++ {
key := []byte(fmt.Sprintf("key-%d", i))
value := []byte(fmt.Sprintf("value-%d", i))
keys[i] = key
mt.Put(key, value, uint64(i))
}
// Mark memtable as immutable
mt.SetImmutable()
// Create random keys for lookup
r := rand.New(rand.NewSource(42)) // Use fixed seed for reproducibility
lookupKeys := make([][]byte, b.N)
for i := 0; i < b.N; i++ {
idx := r.Intn(numEntries)
lookupKeys[i] = keys[idx]
}
// Set up for parallel benchmark
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
// Each goroutine needs its own random sequence
localRand := rand.New(rand.NewSource(rand.Int63()))
i := 0
for pb.Next() {
// Pick a random key from our prepared list
idx := localRand.Intn(len(lookupKeys))
mt.Get(lookupKeys[idx])
i++
}
})
}
func BenchmarkMixedWorkload(b *testing.B) {
// Skip very long benchmarks if testing with -short flag
if testing.Short() {
b.Skip("Skipping mixed workload benchmark in short mode")
}
// This benchmark tests a mixed workload with concurrent reads and writes
mt := NewMemTable()
// Pre-populate with some data
const initialEntries = 50000
keys := make([][]byte, initialEntries)
for i := 0; i < initialEntries; i++ {
key := []byte(fmt.Sprintf("key-%d", i))
value := []byte(fmt.Sprintf("value-%d", i))
keys[i] = key
mt.Put(key, value, uint64(i))
}
// Prepare random operations
readRatio := 0.8 // 80% reads, 20% writes
b.ResetTimer()
// Run the benchmark in parallel mode
b.RunParallel(func(pb *testing.PB) {
// Each goroutine gets its own random number generator
r := rand.New(rand.NewSource(rand.Int63()))
localCount := 0
// Continue until the benchmark is done
for pb.Next() {
// Determine operation: read or write
op := r.Float64()
if op < readRatio {
// Read operation
idx := r.Intn(initialEntries)
mt.Get(keys[idx])
} else {
// Write operation (alternating put and delete)
if localCount%2 == 0 {
// Put
newKey := []byte(fmt.Sprintf("key-new-%d", localCount))
newValue := []byte(fmt.Sprintf("value-new-%d", localCount))
mt.Put(newKey, newValue, uint64(initialEntries+localCount))
} else {
// Delete (use an existing key)
idx := r.Intn(initialEntries)
mt.Delete(keys[idx], uint64(initialEntries+localCount))
}
}
localCount++
}
})
}
func BenchmarkMemPoolGet(b *testing.B) {
cfg := createTestConfig()
cfg.MemTableSize = 1024 * 1024 * 32 // 32MB for benchmark
pool := NewMemTablePool(cfg)
// Create multiple memtables with entries
const entriesPerTable = 50000
const numTables = 3
keys := make([][]byte, entriesPerTable*numTables)
// Fill tables
for t := 0; t < numTables; t++ {
// Fill a table
for i := 0; i < entriesPerTable; i++ {
idx := t*entriesPerTable + i
key := []byte(fmt.Sprintf("key-%d", idx))
value := []byte(fmt.Sprintf("value-%d", idx))
keys[idx] = key
pool.Put(key, value, uint64(idx))
}
// Switch to a new memtable (except for last one)
if t < numTables-1 {
pool.SwitchToNewMemTable()
}
}
// Create random keys for lookup
lookupKeys := make([][]byte, b.N)
r := rand.New(rand.NewSource(42)) // Use fixed seed for reproducibility
for i := 0; i < b.N; i++ {
idx := r.Intn(entriesPerTable * numTables)
lookupKeys[i] = keys[idx]
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
pool.Get(lookupKeys[i])
}
}