From 7c4bb474a8e0ca8432e22e5e135ddb36c5c04793 Mon Sep 17 00:00:00 2001 From: Ibrahim Jarif Date: Tue, 14 Jan 2020 19:52:29 +0530 Subject: [PATCH] Use fastRand instead of locked-rand in skiplist (#1173) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The math/rand package (https://golang.org/src/math/rand/rand.go) uses a global lock to allow concurrent access to the rand object. The PR replaces `math.Rand` with `ristretto/z.FastRand()`. `FastRand` is much faster than `math.Rand` and `rand.New(..)` generators. The change improves concurrent writes to skiplist by ~30% ```go func BenchmarkWrite(b *testing.B) { value := newValue(123) l := NewSkiplist(int64((b.N + 1) * MaxNodeSize)) defer l.DecrRef() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { rng := rand.New(rand.NewSource(time.Now().UnixNano())) for pb.Next() { l.Put(randomKey(rng), y.ValueStruct{Value: value, Meta: 0, UserMeta: 0}) } }) } ``` ``` name old time/op new time/op delta Write-16 657ns ± 3% 441ns ± 1% -32.94% (p=0.000 n=9+10) ``` (cherry picked from commit 9d6512b081d0c172f5e5153b289f2c97458da01c) --- levels_test.go | 22 ++++++++-------------- skl/skl.go | 8 ++++---- skl/skl_test.go | 15 ++++++++++++++- table/table_test.go | 5 ++++- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/levels_test.go b/levels_test.go index 51e7baf72..9b6089ce6 100644 --- a/levels_test.go +++ b/levels_test.go @@ -20,28 +20,22 @@ import ( "math" "testing" - "github.com/dgraph-io/badger/v2/options" - "github.com/dgraph-io/badger/v2/pb" - "github.com/dgraph-io/badger/v2/table" - "github.com/dgraph-io/badger/v2/y" + "github.com/dgraph-io/badger/options" + "github.com/dgraph-io/badger/pb" + "github.com/dgraph-io/badger/table" + "github.com/dgraph-io/badger/y" "github.com/stretchr/testify/require" ) // createAndOpen creates a table with the given data and adds it to the given level. func createAndOpen(db *DB, td []keyValVersion, level int) { - opts := table.Options{ - BlockSize: db.opt.BlockSize, - BloomFalsePositive: db.opt.BloomFalsePositive, - LoadingMode: options.LoadToRAM, - ChkMode: options.NoVerification, - } - b := table.NewTableBuilder(opts) + b := table.NewTableBuilder() // Add all keys and versions to the table. for _, item := range td { key := y.KeyWithTs([]byte(item.key), uint64(item.version)) val := y.ValueStruct{Value: []byte(item.val), Meta: item.meta} - b.Add(key, val, 0) + b.Add(key, val) } fd, err := y.CreateSyncedFile(table.NewFilename(db.lc.reserveFileID(), db.opt.Dir), true) if err != nil { @@ -51,12 +45,12 @@ func createAndOpen(db *DB, td []keyValVersion, level int) { if _, err = fd.Write(b.Finish()); err != nil { panic(err) } - tab, err := table.OpenTable(fd, opts) + tab, err := table.OpenTable(fd, options.LoadToRAM, nil) if err != nil { panic(err) } if err := db.manifest.addChanges([]*pb.ManifestChange{ - newCreateChange(tab.ID(), level, 0, tab.CompressionType()), + newCreateChange(tab.ID(), level, nil), }); err != nil { panic(err) } diff --git a/skl/skl.go b/skl/skl.go index fc2eff982..65647ff5a 100644 --- a/skl/skl.go +++ b/skl/skl.go @@ -34,11 +34,11 @@ package skl import ( "math" - "math/rand" "sync/atomic" "unsafe" "github.com/dgraph-io/badger/y" + "github.com/dgraph-io/ristretto/z" ) const ( @@ -165,9 +165,9 @@ func (s *node) casNextOffset(h int, old, val uint32) bool { // return n != nil && y.CompareKeys(key, n.key) > 0 //} -func randomHeight() int { +func (s *Skiplist) randomHeight() int { h := 1 - for h < maxHeight && rand.Uint32() <= heightIncrease { + for h < maxHeight && z.FastRand() <= heightIncrease { h++ } return h @@ -300,7 +300,7 @@ func (s *Skiplist) Put(key []byte, v y.ValueStruct) { } // We do need to create a new node. - height := randomHeight() + height := s.randomHeight() x := newNode(s.arena, key, v, height) // Try to increase s.height via CAS. diff --git a/skl/skl_test.go b/skl/skl_test.go index b0849f386..a9afcb99b 100644 --- a/skl/skl_test.go +++ b/skl/skl_test.go @@ -458,7 +458,7 @@ func BenchmarkReadWriteMap(b *testing.B) { b.RunParallel(func(pb *testing.PB) { rng := rand.New(rand.NewSource(time.Now().UnixNano())) for pb.Next() { - if rand.Float32() < readFrac { + if rng.Float32() < readFrac { mutex.RLock() _, ok := m[string(randomKey(rng))] mutex.RUnlock() @@ -475,3 +475,16 @@ func BenchmarkReadWriteMap(b *testing.B) { }) } } + +func BenchmarkWrite(b *testing.B) { + value := newValue(123) + l := NewSkiplist(int64((b.N + 1) * MaxNodeSize)) + defer l.DecrRef() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + for pb.Next() { + l.Put(randomKey(rng), y.ValueStruct{Value: value, Meta: 0, UserMeta: 0}) + } + }) +} diff --git a/table/table_test.go b/table/table_test.go index 7e37216a4..f1226fbfa 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -644,7 +644,10 @@ func TestTableChecksum(t *testing.T) { f := buildTestTable(t, "k", 10000) fi, err := f.Stat() require.NoError(t, err, "unable to get file information") - f.WriteAt(rb, rand.Int63n(fi.Size())) + // Write random bytes at random location. + n, err := f.WriteAt(rb, rand.Int63n(fi.Size())) + require.NoError(t, err) + require.Equal(t, n, len(rb)) _, err = OpenTable(f, options.LoadToRAM, []byte("wrong")) if err == nil || !strings.Contains(err.Error(), "checksum") {