2015-03-14 05:57:18 +03:00
|
|
|
// +build lmdb
|
|
|
|
|
2015-02-03 05:17:15 +03:00
|
|
|
package mdb
|
|
|
|
|
|
|
|
import (
|
|
|
|
crand "crypto/rand"
|
|
|
|
"io/ioutil"
|
|
|
|
"math/rand"
|
|
|
|
"os"
|
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
|
|
|
// repeatedly put (overwrite) keys.
|
|
|
|
func BenchmarkTxnPut(b *testing.B) {
|
|
|
|
initRandSource(b)
|
|
|
|
env, path := setupBenchDB(b)
|
|
|
|
defer teardownBenchDB(b, env, path)
|
|
|
|
|
|
|
|
dbi := openBenchDBI(b, env)
|
|
|
|
|
|
|
|
var ps [][]byte
|
|
|
|
|
|
|
|
rc := newRandSourceCursor()
|
|
|
|
txn, err := env.BeginTxn(nil, 0)
|
|
|
|
bMust(b, err, "starting transaction")
|
|
|
|
for i := 0; i < benchDBNumKeys; i++ {
|
|
|
|
k := makeBenchDBKey(&rc)
|
|
|
|
v := makeBenchDBVal(&rc)
|
|
|
|
err := txn.Put(dbi, k, v, 0)
|
|
|
|
ps = append(ps, k, v)
|
|
|
|
bTxnMust(b, txn, err, "putting data")
|
|
|
|
}
|
|
|
|
err = txn.Commit()
|
|
|
|
bMust(b, err, "commiting transaction")
|
|
|
|
|
|
|
|
txn, err = env.BeginTxn(nil, 0)
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
k := ps[rand.Intn(len(ps)/2)*2]
|
|
|
|
v := makeBenchDBVal(&rc)
|
|
|
|
err := txn.Put(dbi, k, v, 0)
|
|
|
|
bTxnMust(b, txn, err, "putting data")
|
|
|
|
}
|
|
|
|
b.StopTimer()
|
|
|
|
err = txn.Commit()
|
|
|
|
bMust(b, err, "commiting transaction")
|
|
|
|
}
|
|
|
|
|
|
|
|
// repeatedly get random keys.
|
|
|
|
func BenchmarkTxnGetRDONLY(b *testing.B) {
|
|
|
|
initRandSource(b)
|
|
|
|
env, path := setupBenchDB(b)
|
|
|
|
defer teardownBenchDB(b, env, path)
|
|
|
|
|
|
|
|
dbi := openBenchDBI(b, env)
|
|
|
|
|
|
|
|
var ps [][]byte
|
|
|
|
|
|
|
|
rc := newRandSourceCursor()
|
|
|
|
txn, err := env.BeginTxn(nil, 0)
|
|
|
|
bMust(b, err, "starting transaction")
|
|
|
|
for i := 0; i < benchDBNumKeys; i++ {
|
|
|
|
k := makeBenchDBKey(&rc)
|
|
|
|
v := makeBenchDBVal(&rc)
|
|
|
|
err := txn.Put(dbi, k, v, 0)
|
|
|
|
ps = append(ps, k, v)
|
|
|
|
bTxnMust(b, txn, err, "putting data")
|
|
|
|
}
|
|
|
|
err = txn.Commit()
|
|
|
|
bMust(b, err, "commiting transaction")
|
|
|
|
|
|
|
|
txn, err = env.BeginTxn(nil, RDONLY)
|
|
|
|
bMust(b, err, "starting transaction")
|
|
|
|
defer txn.Abort()
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
_, err := txn.Get(dbi, ps[rand.Intn(len(ps))])
|
|
|
|
if err == NotFound {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
b.Fatalf("error getting data: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b.StopTimer()
|
|
|
|
}
|
|
|
|
|
|
|
|
// like BenchmarkTxnGetRDONLY, but txn.GetVal() is called instead.
|
|
|
|
func BenchmarkTxnGetValRDONLY(b *testing.B) {
|
|
|
|
initRandSource(b)
|
|
|
|
env, path := setupBenchDB(b)
|
|
|
|
defer teardownBenchDB(b, env, path)
|
|
|
|
|
|
|
|
dbi := openBenchDBI(b, env)
|
|
|
|
|
|
|
|
var ps [][]byte
|
|
|
|
|
|
|
|
rc := newRandSourceCursor()
|
|
|
|
txn, err := env.BeginTxn(nil, 0)
|
|
|
|
bMust(b, err, "starting transaction")
|
|
|
|
for i := 0; i < benchDBNumKeys; i++ {
|
|
|
|
k := makeBenchDBKey(&rc)
|
|
|
|
v := makeBenchDBVal(&rc)
|
|
|
|
err := txn.Put(dbi, k, v, 0)
|
|
|
|
ps = append(ps, k, v)
|
|
|
|
bTxnMust(b, txn, err, "putting data")
|
|
|
|
}
|
|
|
|
err = txn.Commit()
|
|
|
|
bMust(b, err, "commiting transaction")
|
|
|
|
|
|
|
|
txn, err = env.BeginTxn(nil, RDONLY)
|
|
|
|
bMust(b, err, "starting transaction")
|
|
|
|
defer txn.Abort()
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
_, err := txn.GetVal(dbi, ps[rand.Intn(len(ps))])
|
|
|
|
if err == NotFound {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
b.Fatalf("error getting data: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
b.StopTimer()
|
|
|
|
}
|
|
|
|
|
|
|
|
// repeatedly scan all the values in a database.
|
|
|
|
func BenchmarkCursorScanRDONLY(b *testing.B) {
|
|
|
|
initRandSource(b)
|
|
|
|
env, path := setupBenchDB(b)
|
|
|
|
defer teardownBenchDB(b, env, path)
|
|
|
|
|
|
|
|
dbi := openBenchDBI(b, env)
|
|
|
|
|
|
|
|
var ps [][]byte
|
|
|
|
|
|
|
|
rc := newRandSourceCursor()
|
|
|
|
txn, err := env.BeginTxn(nil, 0)
|
|
|
|
bMust(b, err, "starting transaction")
|
|
|
|
for i := 0; i < benchDBNumKeys; i++ {
|
|
|
|
k := makeBenchDBKey(&rc)
|
|
|
|
v := makeBenchDBVal(&rc)
|
|
|
|
err := txn.Put(dbi, k, v, 0)
|
|
|
|
ps = append(ps, k, v)
|
|
|
|
bTxnMust(b, txn, err, "putting data")
|
|
|
|
}
|
|
|
|
err = txn.Commit()
|
|
|
|
bMust(b, err, "commiting transaction")
|
|
|
|
|
|
|
|
txn, err = env.BeginTxn(nil, RDONLY)
|
|
|
|
bMust(b, err, "starting transaction")
|
|
|
|
defer txn.Abort()
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
func() {
|
|
|
|
cur, err := txn.CursorOpen(dbi)
|
|
|
|
bMust(b, err, "opening cursor")
|
|
|
|
defer cur.Close()
|
|
|
|
var count int64
|
|
|
|
for {
|
|
|
|
_, _, err := cur.Get(nil, nil, NEXT)
|
|
|
|
if err == NotFound {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
b.Fatalf("error getting data: %v", err)
|
|
|
|
}
|
|
|
|
count++
|
|
|
|
}
|
|
|
|
if count != benchDBNumKeys {
|
|
|
|
b.Fatalf("unexpected number of keys: %d", count)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
b.StopTimer()
|
|
|
|
}
|
|
|
|
|
|
|
|
// like BenchmarkCursoreScanRDONLY, but cursor.GetVal() is called instead.
|
|
|
|
func BenchmarkCursorScanValRDONLY(b *testing.B) {
|
|
|
|
initRandSource(b)
|
|
|
|
env, path := setupBenchDB(b)
|
|
|
|
defer teardownBenchDB(b, env, path)
|
|
|
|
|
|
|
|
dbi := openBenchDBI(b, env)
|
|
|
|
|
|
|
|
var ps [][]byte
|
|
|
|
|
|
|
|
rc := newRandSourceCursor()
|
|
|
|
txn, err := env.BeginTxn(nil, 0)
|
|
|
|
bMust(b, err, "starting transaction")
|
|
|
|
for i := 0; i < benchDBNumKeys; i++ {
|
|
|
|
k := makeBenchDBKey(&rc)
|
|
|
|
v := makeBenchDBVal(&rc)
|
|
|
|
err := txn.Put(dbi, k, v, 0)
|
|
|
|
ps = append(ps, k, v)
|
|
|
|
bTxnMust(b, txn, err, "putting data")
|
|
|
|
}
|
|
|
|
err = txn.Commit()
|
|
|
|
bMust(b, err, "commiting transaction")
|
|
|
|
|
|
|
|
txn, err = env.BeginTxn(nil, RDONLY)
|
|
|
|
bMust(b, err, "starting transaction")
|
|
|
|
defer txn.Abort()
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
|
|
func() {
|
|
|
|
cur, err := txn.CursorOpen(dbi)
|
|
|
|
bMust(b, err, "opening cursor")
|
|
|
|
defer cur.Close()
|
|
|
|
var count int64
|
|
|
|
for {
|
|
|
|
_, _, err := cur.GetVal(nil, nil, NEXT)
|
|
|
|
if err == NotFound {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
b.Fatalf("error getting data: %v", err)
|
|
|
|
}
|
|
|
|
count++
|
|
|
|
}
|
|
|
|
if count != benchDBNumKeys {
|
|
|
|
b.Fatalf("unexpected number of keys: %d", count)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
b.StopTimer()
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupBenchDB(b *testing.B) (*Env, string) {
|
|
|
|
env, err := NewEnv()
|
|
|
|
bMust(b, err, "creating env")
|
|
|
|
err = env.SetMaxDBs(26)
|
|
|
|
bMust(b, err, "setting max dbs")
|
|
|
|
err = env.SetMapSize(1 << 30) // 1GB
|
|
|
|
bMust(b, err, "sizing env")
|
|
|
|
path, err := ioutil.TempDir("", "mdb_test-bench-")
|
|
|
|
bMust(b, err, "creating temp directory")
|
|
|
|
err = env.Open(path, 0, 0644)
|
|
|
|
if err != nil {
|
|
|
|
teardownBenchDB(b, env, path)
|
|
|
|
}
|
|
|
|
bMust(b, err, "opening database")
|
|
|
|
return env, path
|
|
|
|
}
|
|
|
|
|
|
|
|
func openBenchDBI(b *testing.B, env *Env) DBI {
|
|
|
|
txn, err := env.BeginTxn(nil, 0)
|
|
|
|
bMust(b, err, "starting transaction")
|
|
|
|
name := "benchmark"
|
|
|
|
dbi, err := txn.DBIOpen(&name, CREATE)
|
|
|
|
if err != nil {
|
|
|
|
txn.Abort()
|
|
|
|
b.Fatalf("error opening dbi: %v", err)
|
|
|
|
}
|
|
|
|
err = txn.Commit()
|
|
|
|
bMust(b, err, "commiting transaction")
|
|
|
|
return dbi
|
|
|
|
}
|
|
|
|
|
|
|
|
func teardownBenchDB(b *testing.B, env *Env, path string) {
|
|
|
|
env.Close()
|
|
|
|
os.RemoveAll(path)
|
|
|
|
}
|
|
|
|
|
|
|
|
func randBytes(n int) []byte {
|
|
|
|
p := make([]byte, n)
|
|
|
|
crand.Read(p)
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
|
|
|
func bMust(b *testing.B, err error, action string) {
|
|
|
|
if err != nil {
|
|
|
|
b.Fatalf("error %s: %v", action, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func bTxnMust(b *testing.B, txn *Txn, err error, action string) {
|
|
|
|
if err != nil {
|
|
|
|
txn.Abort()
|
|
|
|
b.Fatalf("error %s: %v", action, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const randSourceSize = 500 << 20 // size of the 'entropy pool' for random byte generation.
|
|
|
|
const benchDBNumKeys = 100000 // number of keys to store in benchmark databases
|
|
|
|
const benchDBMaxKeyLen = 30 // maximum length for database keys (size is limited by MDB)
|
|
|
|
const benchDBMaxValLen = 2000 // maximum lengh for database values
|
|
|
|
|
|
|
|
func makeBenchDBKey(c *randSourceCursor) []byte {
|
|
|
|
return c.NBytes(rand.Intn(benchDBMaxKeyLen) + 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeBenchDBVal(c *randSourceCursor) []byte {
|
|
|
|
return c.NBytes(rand.Intn(benchDBMaxValLen) + 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// holds a bunch of random bytes so repeated generation af 'random' slices is
|
|
|
|
// cheap. acts as a ring which can be read from (although doesn't implement io.Reader).
|
|
|
|
var randSource [randSourceSize]byte
|
|
|
|
|
|
|
|
func initRandSource(b *testing.B) {
|
|
|
|
if randSource[0] == 0 && randSource[1] == 0 && randSource[2] == 0 && randSource[3] == 0 {
|
|
|
|
b.Logf("initializing random source data")
|
|
|
|
n, err := crand.Read(randSource[:])
|
|
|
|
bMust(b, err, "initializing random source")
|
|
|
|
if n < len(randSource) {
|
|
|
|
b.Fatalf("unable to read enough random source data %d", n)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// acts as a simple byte slice generator.
|
|
|
|
type randSourceCursor int
|
|
|
|
|
|
|
|
func newRandSourceCursor() randSourceCursor {
|
|
|
|
i := rand.Intn(randSourceSize)
|
|
|
|
return randSourceCursor(i)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *randSourceCursor) NBytes(n int) []byte {
|
|
|
|
i := int(*c)
|
|
|
|
if n >= randSourceSize {
|
|
|
|
panic("rand size too big")
|
|
|
|
}
|
|
|
|
*c = (*c + randSourceCursor(n)) % randSourceSize
|
|
|
|
_n := i + n - randSourceSize
|
|
|
|
if _n > 0 {
|
|
|
|
p := make([]byte, n)
|
|
|
|
m := copy(p, randSource[i:])
|
|
|
|
copy(p[m:], randSource[:])
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
return randSource[i : i+n]
|
|
|
|
}
|