diff --git a/Makefile b/Makefile index 67635ae..3fec2c7 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ BRANCH=`git rev-parse --abbrev-ref HEAD` COMMIT=`git rev-parse --short HEAD` GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)" +default: build + bench: go test -v -test.run=NOTHINCONTAINSTHIS -test.bench=$(BENCH) diff --git a/bucket.go b/bucket.go index 877c310..2338a8b 100644 --- a/bucket.go +++ b/bucket.go @@ -76,8 +76,8 @@ type bucket struct { // newBucket returns a new bucket associated with a transaction. func newBucket(tx *Tx) Bucket { var b = Bucket{tx: tx} - b.buckets = make(map[string]*Bucket) if tx.writable { + b.buckets = make(map[string]*Bucket) b.nodes = make(map[pgid]*node) } return b @@ -115,8 +115,10 @@ func (b *Bucket) Cursor() *Cursor { // Bucket retrieves a nested bucket by name. // Returns nil if the bucket does not exist. func (b *Bucket) Bucket(name []byte) *Bucket { - if child := b.buckets[string(name)]; child != nil { - return child + if b.buckets != nil { + if child := b.buckets[string(name)]; child != nil { + return child + } } // Move cursor to key. @@ -130,7 +132,9 @@ func (b *Bucket) Bucket(name []byte) *Bucket { // Otherwise create a bucket and cache it. var child = b.openBucket(v) - b.buckets[string(name)] = child + if b.buckets != nil { + b.buckets[string(name)] = child + } return child } @@ -139,8 +143,15 @@ func (b *Bucket) Bucket(name []byte) *Bucket { // from a parent into a Bucket func (b *Bucket) openBucket(value []byte) *Bucket { var child = newBucket(b.tx) - child.bucket = &bucket{} - *child.bucket = *(*bucket)(unsafe.Pointer(&value[0])) + + // If this is a writable transaction then we need to copy the bucket entry. + // Read-only transactions can point directly at the mmap entry. + if b.tx.writable { + child.bucket = &bucket{} + *child.bucket = *(*bucket)(unsafe.Pointer(&value[0])) + } else { + child.bucket = (*bucket)(unsafe.Pointer(&value[0])) + } // Save a reference to the inline page if the bucket is inline. if child.root == 0 { diff --git a/cmd/bolt/bench.go b/cmd/bolt/bench.go index 6379144..5d0dbec 100644 --- a/cmd/bolt/bench.go +++ b/cmd/bolt/bench.go @@ -100,6 +100,10 @@ func benchWrite(db *bolt.DB, options *BenchOptions, results *BenchResults) error err = benchWriteSequential(db, options, results) case "rnd": err = benchWriteRandom(db, options, results) + case "seq-nest": + err = benchWriteSequentialNested(db, options, results) + case "rnd-nest": + err = benchWriteRandomNested(db, options, results) default: return fmt.Errorf("invalid write mode: %s", options.WriteMode) } @@ -119,6 +123,16 @@ func benchWriteRandom(db *bolt.DB, options *BenchOptions, results *BenchResults) return benchWriteWithSource(db, options, results, func() uint32 { return r.Uint32() }) } +func benchWriteSequentialNested(db *bolt.DB, options *BenchOptions, results *BenchResults) error { + var i = uint32(0) + return benchWriteNestedWithSource(db, options, results, func() uint32 { i++; return i }) +} + +func benchWriteRandomNested(db *bolt.DB, options *BenchOptions, results *BenchResults) error { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + return benchWriteNestedWithSource(db, options, results, func() uint32 { return r.Uint32() }) +} + func benchWriteWithSource(db *bolt.DB, options *BenchOptions, results *BenchResults, keySource func() uint32) error { results.WriteOps = options.Iterations @@ -144,6 +158,35 @@ func benchWriteWithSource(db *bolt.DB, options *BenchOptions, results *BenchResu return nil } +func benchWriteNestedWithSource(db *bolt.DB, options *BenchOptions, results *BenchResults, keySource func() uint32) error { + results.WriteOps = options.Iterations + + for i := 0; i < options.Iterations; i += options.BatchSize { + err := db.Update(func(tx *bolt.Tx) error { + top, _ := tx.CreateBucketIfNotExists(benchBucketName) + + var name = make([]byte, options.KeySize) + binary.BigEndian.PutUint32(name, keySource()) + b, _ := top.CreateBucketIfNotExists(name) + + for j := 0; j < options.BatchSize; j++ { + var key = make([]byte, options.KeySize) + var value = make([]byte, options.ValueSize) + binary.BigEndian.PutUint32(key, keySource()) + if err := b.Put(key, value); err != nil { + return err + } + } + + return nil + }) + if err != nil { + return err + } + } + return nil +} + // Reads from the database. func benchRead(db *bolt.DB, options *BenchOptions, results *BenchResults) error { var err error @@ -151,7 +194,11 @@ func benchRead(db *bolt.DB, options *BenchOptions, results *BenchResults) error switch options.ReadMode { case "seq": - err = benchReadSequential(db, options, results) + if options.WriteMode == "seq-nest" || options.WriteMode == "rnd-nest" { + err = benchReadSequentialNested(db, options, results) + } else { + err = benchReadSequential(db, options, results) + } default: return fmt.Errorf("invalid read mode: %s", options.ReadMode) } @@ -191,6 +238,40 @@ func benchReadSequential(db *bolt.DB, options *BenchOptions, results *BenchResul }) } +func benchReadSequentialNested(db *bolt.DB, options *BenchOptions, results *BenchResults) error { + return db.View(func(tx *bolt.Tx) error { + var t = time.Now() + + for { + var count int + var top = tx.Bucket(benchBucketName) + top.ForEach(func(name, _ []byte) error { + c := top.Bucket(name).Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + if v == nil { + return errors.New("invalid value") + } + count++ + } + return nil + }) + + if options.WriteMode == "seq-nest" && count != options.Iterations { + return fmt.Errorf("read seq-nest: iter mismatch: expected %d, got %d", options.Iterations, count) + } + + results.ReadOps += count + + // Make sure we do this for at least a second. + if time.Since(t) >= time.Second { + break + } + } + + return nil + }) +} + // Starts all profiles set on the options. func benchStartProfiling(options *BenchOptions) { var err error