diff --git a/bench_test.go b/bench_test.go index d19795f..f841884 100644 --- a/bench_test.go +++ b/bench_test.go @@ -32,8 +32,11 @@ const ( // i.e., don't benchmark if we will need to clone the store every N operations and N < benchMinNumMutableKeys. benchMinNumMutableKeys = 1 << 10 - // The maximum size of created absent and bounds slices, 64K. - genMaxSize = 1 << 16 + // The maximum size of created absent key slices, 256K. + genAbsentMaxSize = 1 << 18 + + // The maximum size of created bounds slices, 64K. + genBoundsMaxSize = 1 << 16 // Only the lowercase characters a-z and newlines appear in this file. filenameWords = "testdata/words_alpha.txt" @@ -268,62 +271,52 @@ func entriesFromFile(filename string) map[string]byte { return entries } -func createPresent(entries map[string]byte, random *rand.Rand) []keySet { - present := []keySet{} +func createPresent(entries map[string]byte, random *rand.Rand) keySet { + present := keySet{} for k := range entries { - keyLen := len(k) - for i := len(present); i <= keyLen; i++ { - present = append(present, keySet{}) - } - present[keyLen] = append(present[keyLen], []byte(k)) - } - for _, keys := range present { - slices.SortFunc(keys, bytes.Compare) - shuffle(keys, random) + present = append(present, []byte(k)) } + slices.SortFunc(present, bytes.Compare) + shuffle(present, random) return present } -func createAbsent(entries map[string]byte, maxKeyLen int, random *rand.Rand) []keySet { - absent := make([]keySet, maxKeyLen+1) +func createAbsent(entries map[string]byte, random *rand.Rand) keySet { + absent := keySet{} // set absent for key lengths 0-2, everything not present if _, ok := entries[""]; !ok { - absent[0] = append(absent[0], []byte{}) + absent = append(absent, []byte{}) } for hi := range 256 { key := []byte{byte(hi)} if _, ok := entries[string(key)]; !ok { - absent[1] = append(absent[1], key) + absent = append(absent, key) } for low := range 256 { key := []byte{byte(hi), byte(low)} if _, ok := entries[string(key)]; !ok { - absent[2] = append(absent[2], key) + absent = append(absent, key) } } } - // set absent for key lengths 3+, randomly generated - // keep adding until each absent[keyLen] has maxGenSize keys - for keyLen := range absent { - if keyLen < 3 { - continue - } - for len(absent[keyLen]) < genMaxSize { - key := randomFixedLengthKey(keyLen, random) - if _, ok := entries[string(key)]; !ok { - absent[keyLen] = append(absent[keyLen], key) - } + // Add longer keys. + for len(absent) < genAbsentMaxSize { + key := randomFixedLengthKey(8, random) + if _, ok := entries[string(key)]; !ok { + absent = append(absent, key) } } + slices.SortFunc(absent, bytes.Compare) + shuffle(absent, random) return absent } //nolint:nonamedreturns func createBounds(keys keySet) (forward, reverse []Bounds) { - for i := range genMaxSize { + for i := range genBoundsMaxSize { begin := keys[(2*i)%len(keys)] end := keys[(2*i+1)%len(keys)] switch cmp := bytes.Compare(begin, end); { @@ -363,8 +356,10 @@ func createBenchStoreConfig(corpusName string, entries map[string]byte) *storeCo ref.Set([]byte(k), v) } present := createPresent(entries, random) - absent := createAbsent(entries, len(present)-1, random) - keys := append(slices.Concat(present...), slices.Concat(absent...)...) + absent := createAbsent(entries, random) + keys := make([][]byte, len(present)+len(absent)) + copy(keys, present) + copy(keys[:len(present)], absent) shuffle(keys, random) forward, reverse := createBounds(keys) return &storeConfig{ @@ -503,69 +498,64 @@ func BenchmarkSet(b *testing.B) { for _, bench := range createTestStores(benchStoreConfigs) { original := bench.store b.Run(bench.name, func(b *testing.B) { - for keyLen := 8; keyLen < len(bench.config.present); keyLen += 4 { - b.Run(fmt.Sprintf("keyLen=%d/existing=true", keyLen), func(b *testing.B) { - present := bench.config.present[keyLen] - if len(present) == 0 { - b.Skipf("no present keys of length %d", keyLen) - } - store := original.Clone() - i := 0 - for b.Loop() { - store.Set(present[i%len(present)], 42) - i++ - } - }) - b.Run(fmt.Sprintf("keyLen=%d/existing=false", keyLen), func(b *testing.B) { - absent := bench.config.absent[keyLen] - if len(absent) == 0 { - b.Skipf("no absent keys of length %d", keyLen) - } - store := original.Clone() - i := 0 - for b.Loop() { - if i%len(absent) == 0 && i > 0 { - b.StopTimer() - store = original.Clone() - b.StartTimer() - } - store.Set(absent[i%len(absent)], 42) - i++ + b.Run("existing=true", func(b *testing.B) { + present := bench.config.present + if len(present) == 0 { + b.Skipf("no present keys") + } + store := original.Clone() + i := 0 + for b.Loop() { + store.Set(present[i%len(present)], 42) + i++ + } + }) + b.Run("existing=false", func(b *testing.B) { + absent := bench.config.absent + if len(absent) == 0 { + b.Skipf("no absent keys") + } + store := original.Clone() + i := 0 + for b.Loop() { + if i%len(absent) == 0 && i > 0 { + b.StopTimer() + store = original.Clone() + b.StartTimer() } - }) - } + store.Set(absent[i%len(absent)], 42) + i++ + } + }) }) } } -//nolint:gocognit func BenchmarkGet(b *testing.B) { for _, bench := range createTestStores(benchStoreConfigs) { b.Run(bench.name, func(b *testing.B) { - for keyLen := 8; keyLen < len(bench.config.present); keyLen += 4 { - b.Run(fmt.Sprintf("keyLen=%d/existing=true", keyLen), func(b *testing.B) { - present := bench.config.present[keyLen] - if len(present) == 0 { - b.Skipf("no present keys of length %d", keyLen) - } - i := 0 - for b.Loop() { - bench.store.Get(present[i%len(present)]) - i++ - } - }) - b.Run(fmt.Sprintf("keyLen=%d/existing=false", keyLen), func(b *testing.B) { - absent := bench.config.absent[keyLen] - if len(absent) == 0 { - b.Skipf("no absent keys of length %d", keyLen) - } - i := 0 - for b.Loop() { - bench.store.Get(absent[i%len(absent)]) - i++ - } - }) - } + b.Run("existing=true", func(b *testing.B) { + present := bench.config.present + if len(present) == 0 { + b.Skipf("no present keys") + } + i := 0 + for b.Loop() { + bench.store.Get(present[i%len(present)]) + i++ + } + }) + b.Run("existing=false", func(b *testing.B) { + absent := bench.config.absent + if len(absent) == 0 { + b.Skipf("no absent keys") + } + i := 0 + for b.Loop() { + bench.store.Get(absent[i%len(absent)]) + i++ + } + }) }) } } @@ -575,40 +565,35 @@ func BenchmarkDelete(b *testing.B) { for _, bench := range createTestStores(benchStoreConfigs) { original := bench.store b.Run(bench.name, func(b *testing.B) { - for keyLen := 8; keyLen < len(bench.config.present); keyLen += 4 { - b.Run(fmt.Sprintf("keyLen=%d/existing=true", keyLen), func(b *testing.B) { - present := bench.config.present[keyLen] - if len(present) == 0 { - b.Skipf("no present keys of length %d", keyLen) - } - if len(present) < benchMinNumMutableKeys { - b.Skipf("insufficient present keys of length %d: %d", keyLen, len(present)) - } - store := original.Clone() - i := 0 - for b.Loop() { - if i%len(present) == 0 && i > 0 { - b.StopTimer() - store = original.Clone() - b.StartTimer() - } - store.Delete(present[i%len(present)]) - i++ - } - }) - b.Run(fmt.Sprintf("keyLen=%d/existing=false", keyLen), func(b *testing.B) { - absent := bench.config.absent[keyLen] - if len(absent) == 0 { - b.Skipf("no absent keys of length %d", keyLen) - } - store := original.Clone() - i := 0 - for b.Loop() { - store.Delete(absent[i%len(absent)]) - i++ + b.Run("existing=true", func(b *testing.B) { + present := bench.config.present + if len(present) < benchMinNumMutableKeys { + b.Skipf("insufficient present keys: %d", len(present)) + } + store := original.Clone() + i := 0 + for b.Loop() { + if i%len(present) == 0 && i > 0 { + b.StopTimer() + store = original.Clone() + b.StartTimer() } - }) - } + store.Delete(present[i%len(present)]) + i++ + } + }) + b.Run("existing=false", func(b *testing.B) { + absent := bench.config.absent + if len(absent) == 0 { + b.Skipf("no absent keys") + } + store := original.Clone() + i := 0 + for b.Loop() { + store.Delete(absent[i%len(absent)]) + i++ + } + }) }) } } diff --git a/kv_test.go b/kv_test.go index a55e0a0..72ee956 100644 --- a/kv_test.go +++ b/kv_test.go @@ -32,9 +32,7 @@ type ( size int ref *reference - // present/absent[i] = a set of keys of length i that are present/absent. - // For denser stores, there may be no absent keys of shorter lengths. - present, absent []keySet + present, absent keySet // forward/reverse Bounds instances to test Range. forward, reverse []Bounds @@ -52,9 +50,6 @@ type ( const ( zero = byte(0) - - // The max key length from testPresentKeys, testAbsentKeys, and testNearKeys. - testMaxKeyLen = 3 ) var ( @@ -241,26 +236,22 @@ func createTestStoreConfigs() []*storeConfig { fmt.Sprintf("sub-store=%0*b", len(testPresentKeys), keyBits), bits.OnesCount(uint(keyBits)), newReference(), - make([]keySet, testMaxKeyLen+1), - make([]keySet, testMaxKeyLen+1), + keySet{}, + keySet{}, forward, reverse, } mask := 0x01 for i, k := range testPresentKeys { - keyLen := len(k) if keyBits&mask != 0 { config.ref.Set(k, byte(i)) - config.present[keyLen] = append(config.present[keyLen], k) + config.present = append(config.present, k) } else { - config.absent[keyLen] = append(config.absent[keyLen], k) + config.absent = append(config.absent, k) } mask <<= 1 } - for _, k := range testAbsentKeys { - keyLen := len(k) - config.absent[keyLen] = append(config.absent[keyLen], k) - } + config.absent = append(config.absent, testAbsentKeys...) result = append(result, &config) } return result @@ -542,12 +533,10 @@ func TestStores(t *testing.T) { }) } - for _, keys := range test.config.absent { - for _, k := range keys { - t.Run("op=absent/key="+kv.KeyName(k), func(t *testing.T) { - assertAbsent(t, k, store) - }) - } + for _, k := range test.config.absent { + t.Run("op=absent/key="+kv.KeyName(k), func(t *testing.T) { + assertAbsent(t, k, store) + }) } t.Run("op=range", func(t *testing.T) { @@ -564,7 +553,6 @@ func TestStores(t *testing.T) { } } -//nolint:gocognit func TestClone(t *testing.T) { t.Parallel() for _, test := range createTestStores(testStoreConfigs) { @@ -582,10 +570,8 @@ func TestClone(t *testing.T) { store.Delete(k) } assertIterEmpty(t, store.Range(forwardAll)) - for _, keys := range test.config.absent { - for i, k := range keys { - store.Set(k, byte(i)) - } + for i, k := range test.config.absent { + store.Set(k, byte(i)) } assertSame(t, test.config.ref, original) @@ -595,10 +581,8 @@ func TestClone(t *testing.T) { original.Delete(k) } assertIterEmpty(t, original.Range(forwardAll)) - for _, keys := range test.config.absent { - for i, k := range keys { - original.Set(k, byte(i)) - } + for i, k := range test.config.absent { + original.Set(k, byte(i)) } assertSame(t, test.config.ref, store) })