mirror of https://github.com/etcd-io/bbolt.git
Remove allocations from read-only buckets.
parent
12b36fe70c
commit
06b01dbb67
2
Makefile
2
Makefile
|
@ -5,6 +5,8 @@ BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||||
COMMIT=`git rev-parse --short HEAD`
|
COMMIT=`git rev-parse --short HEAD`
|
||||||
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
|
GOLDFLAGS="-X main.branch $(BRANCH) -X main.commit $(COMMIT)"
|
||||||
|
|
||||||
|
default: build
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
go test -v -test.run=NOTHINCONTAINSTHIS -test.bench=$(BENCH)
|
go test -v -test.run=NOTHINCONTAINSTHIS -test.bench=$(BENCH)
|
||||||
|
|
||||||
|
|
13
bucket.go
13
bucket.go
|
@ -76,8 +76,8 @@ type bucket struct {
|
||||||
// newBucket returns a new bucket associated with a transaction.
|
// newBucket returns a new bucket associated with a transaction.
|
||||||
func newBucket(tx *Tx) Bucket {
|
func newBucket(tx *Tx) Bucket {
|
||||||
var b = Bucket{tx: tx}
|
var b = Bucket{tx: tx}
|
||||||
b.buckets = make(map[string]*Bucket)
|
|
||||||
if tx.writable {
|
if tx.writable {
|
||||||
|
b.buckets = make(map[string]*Bucket)
|
||||||
b.nodes = make(map[pgid]*node)
|
b.nodes = make(map[pgid]*node)
|
||||||
}
|
}
|
||||||
return b
|
return b
|
||||||
|
@ -115,9 +115,11 @@ func (b *Bucket) Cursor() *Cursor {
|
||||||
// Bucket retrieves a nested bucket by name.
|
// Bucket retrieves a nested bucket by name.
|
||||||
// Returns nil if the bucket does not exist.
|
// Returns nil if the bucket does not exist.
|
||||||
func (b *Bucket) Bucket(name []byte) *Bucket {
|
func (b *Bucket) Bucket(name []byte) *Bucket {
|
||||||
|
if b.buckets != nil {
|
||||||
if child := b.buckets[string(name)]; child != nil {
|
if child := b.buckets[string(name)]; child != nil {
|
||||||
return child
|
return child
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Move cursor to key.
|
// Move cursor to key.
|
||||||
c := b.Cursor()
|
c := b.Cursor()
|
||||||
|
@ -130,7 +132,9 @@ func (b *Bucket) Bucket(name []byte) *Bucket {
|
||||||
|
|
||||||
// Otherwise create a bucket and cache it.
|
// Otherwise create a bucket and cache it.
|
||||||
var child = b.openBucket(v)
|
var child = b.openBucket(v)
|
||||||
|
if b.buckets != nil {
|
||||||
b.buckets[string(name)] = child
|
b.buckets[string(name)] = child
|
||||||
|
}
|
||||||
|
|
||||||
return child
|
return child
|
||||||
}
|
}
|
||||||
|
@ -139,8 +143,15 @@ func (b *Bucket) Bucket(name []byte) *Bucket {
|
||||||
// from a parent into a Bucket
|
// from a parent into a Bucket
|
||||||
func (b *Bucket) openBucket(value []byte) *Bucket {
|
func (b *Bucket) openBucket(value []byte) *Bucket {
|
||||||
var child = newBucket(b.tx)
|
var child = newBucket(b.tx)
|
||||||
|
|
||||||
|
// 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{}
|
||||||
*child.bucket = *(*bucket)(unsafe.Pointer(&value[0]))
|
*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.
|
// Save a reference to the inline page if the bucket is inline.
|
||||||
if child.root == 0 {
|
if child.root == 0 {
|
||||||
|
|
|
@ -100,6 +100,10 @@ func benchWrite(db *bolt.DB, options *BenchOptions, results *BenchResults) error
|
||||||
err = benchWriteSequential(db, options, results)
|
err = benchWriteSequential(db, options, results)
|
||||||
case "rnd":
|
case "rnd":
|
||||||
err = benchWriteRandom(db, options, results)
|
err = benchWriteRandom(db, options, results)
|
||||||
|
case "seq-nest":
|
||||||
|
err = benchWriteSequentialNested(db, options, results)
|
||||||
|
case "rnd-nest":
|
||||||
|
err = benchWriteRandomNested(db, options, results)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid write mode: %s", options.WriteMode)
|
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() })
|
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 {
|
func benchWriteWithSource(db *bolt.DB, options *BenchOptions, results *BenchResults, keySource func() uint32) error {
|
||||||
results.WriteOps = options.Iterations
|
results.WriteOps = options.Iterations
|
||||||
|
|
||||||
|
@ -144,6 +158,35 @@ func benchWriteWithSource(db *bolt.DB, options *BenchOptions, results *BenchResu
|
||||||
return nil
|
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.
|
// Reads from the database.
|
||||||
func benchRead(db *bolt.DB, options *BenchOptions, results *BenchResults) error {
|
func benchRead(db *bolt.DB, options *BenchOptions, results *BenchResults) error {
|
||||||
var err error
|
var err error
|
||||||
|
@ -151,7 +194,11 @@ func benchRead(db *bolt.DB, options *BenchOptions, results *BenchResults) error
|
||||||
|
|
||||||
switch options.ReadMode {
|
switch options.ReadMode {
|
||||||
case "seq":
|
case "seq":
|
||||||
|
if options.WriteMode == "seq-nest" || options.WriteMode == "rnd-nest" {
|
||||||
|
err = benchReadSequentialNested(db, options, results)
|
||||||
|
} else {
|
||||||
err = benchReadSequential(db, options, results)
|
err = benchReadSequential(db, options, results)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid read mode: %s", options.ReadMode)
|
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.
|
// Starts all profiles set on the options.
|
||||||
func benchStartProfiling(options *BenchOptions) {
|
func benchStartProfiling(options *BenchOptions) {
|
||||||
var err error
|
var err error
|
||||||
|
|
Loading…
Reference in New Issue