From b6fdbc2674e77b9bcf76c87caff914819f9b485c Mon Sep 17 00:00:00 2001 From: Lorenz Bauer Date: Fri, 1 Apr 2016 10:38:55 +0100 Subject: [PATCH 1/2] lmdb: Add benchmark for Get on missing keys --- lmdb/bench_test.go | 27 +++++++++++++++++++++++++++ lmdb/cursor_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/lmdb/bench_test.go b/lmdb/bench_test.go index 886b518b..25378b10 100644 --- a/lmdb/bench_test.go +++ b/lmdb/bench_test.go @@ -271,6 +271,33 @@ func BenchmarkTxn_Get_raw_ro(b *testing.B) { } } +// Get a missing key, with RawRead turned on. +func BenchmarkTxn_Get_missing_raw(b *testing.B) { + env := setup(b) + defer clean(env, b) + + dbi := openBenchDBI(b, env) + + err := env.View(func(txn *Txn) (err error) { + txn.RawRead = true + key := []byte("does not exist") + + b.ResetTimer() + defer b.StopTimer() + for i := 0; i < b.N; i++ { + _, err := txn.Get(dbi, key) + if !IsNotFound(err) { + b.Fatalf("found non-existent key: %v", err) + } + } + return nil + }) + if err != nil { + b.Error(err) + return + } +} + // repeatedly scan all the values in a database. func BenchmarkScan_ro(b *testing.B) { initRandSource(b) diff --git a/lmdb/cursor_test.go b/lmdb/cursor_test.go index 0d387c14..68f72ffe 100644 --- a/lmdb/cursor_test.go +++ b/lmdb/cursor_test.go @@ -811,3 +811,45 @@ func BenchmarkCursor_Renew(b *testing.B) { return nil }) } + +func BenchmarkCursor_Get_missing_raw(b *testing.B) { + env := setup(b) + defer clean(env, b) + + var db DBI + err := env.View(func(txn *Txn) (err error) { + db, err = txn.OpenRoot(0) + if err != nil { + return err + } + return nil + }) + if err != nil { + b.Error(err) + return + } + + err = env.View(func(txn *Txn) (err error) { + cur, err := txn.OpenCursor(db) + if err != nil { + return err + } + defer cur.Close() + + key := []byte("does not exist") + + b.ResetTimer() + defer b.StopTimer() + + for i := 0; i < b.N; i++ { + if _, _, err := cur.Get(key, nil, SetKey); !IsNotFound(err) { + b.Fatal("found key that should not exist") + } + } + return + }) + if err != nil { + b.Error(err) + return + } +} From bb0d41a95b66650a9ee161fcaff1bc693f978081 Mon Sep 17 00:00:00 2001 From: Lorenz Bauer Date: Fri, 1 Apr 2016 11:59:51 +0100 Subject: [PATCH 2/2] lmdb: Return dedicated ErrNotFound from Txn.Get and Cursor.Get This avoids allocation of a new Error every time we look up a key that does not exist, saving two allocations per lookup. --- lmdb/cursor.go | 32 ++++++++++++++++++++------------ lmdb/error.go | 8 +++++++- lmdb/txn.go | 15 +++++++++++++++ 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/lmdb/cursor.go b/lmdb/cursor.go index ac4df7a7..d979d6ca 100644 --- a/lmdb/cursor.go +++ b/lmdb/cursor.go @@ -137,16 +137,27 @@ func (c *Cursor) DBI() DBI { // // Get ignores setval if setkey is empty. // +// Returns ErrNotFound if setkey is not present. +// // See mdb_cursor_get. func (c *Cursor) Get(setkey, setval []byte, op uint) (key, val []byte, err error) { + var ret C.int switch { case len(setkey) == 0: - err = c.getVal0(op) + ret = c.getVal0(op) case len(setval) == 0: - err = c.getVal1(setkey, op) + ret = c.getVal1(setkey, op) default: - err = c.getVal2(setkey, setval, op) + ret = c.getVal2(setkey, setval, op) + } + + if ret == C.MDB_NOTFOUND { + *c.txn.key = C.MDB_val{} + *c.txn.val = C.MDB_val{} + return nil, nil, ErrNotFound } + + err = operrno("mdb_cursor_get", ret) if err != nil { *c.txn.key = C.MDB_val{} *c.txn.val = C.MDB_val{} @@ -163,38 +174,35 @@ func (c *Cursor) Get(setkey, setval []byte, op uint) (key, val []byte, err error // data for reference (Next, First, Last, etc). // // See mdb_cursor_get. -func (c *Cursor) getVal0(op uint) error { - ret := C.mdb_cursor_get(c._c, c.txn.key, c.txn.val, C.MDB_cursor_op(op)) - return operrno("mdb_cursor_get", ret) +func (c *Cursor) getVal0(op uint) C.int { + return C.mdb_cursor_get(c._c, c.txn.key, c.txn.val, C.MDB_cursor_op(op)) } // getVal1 retrieves items from the database using key data for reference // (Set, SetRange, etc). // // See mdb_cursor_get. -func (c *Cursor) getVal1(setkey []byte, op uint) error { - ret := C.lmdbgo_mdb_cursor_get1( +func (c *Cursor) getVal1(setkey []byte, op uint) C.int { + return C.lmdbgo_mdb_cursor_get1( c._c, unsafe.Pointer(&setkey[0]), C.size_t(len(setkey)), c.txn.key, c.txn.val, C.MDB_cursor_op(op), ) - return operrno("mdb_cursor_get", ret) } // getVal2 retrieves items from the database using key and value data for // reference (GetBoth, GetBothRange, etc). // // See mdb_cursor_get. -func (c *Cursor) getVal2(setkey, setval []byte, op uint) error { - ret := C.lmdbgo_mdb_cursor_get2( +func (c *Cursor) getVal2(setkey, setval []byte, op uint) C.int { + return C.lmdbgo_mdb_cursor_get2( c._c, unsafe.Pointer(&setkey[0]), C.size_t(len(setkey)), unsafe.Pointer(&setval[0]), C.size_t(len(setval)), c.txn.key, c.txn.val, C.MDB_cursor_op(op), ) - return operrno("mdb_cursor_get", ret) } func (c *Cursor) putNilKey(flags uint) error { diff --git a/lmdb/error.go b/lmdb/error.go index f9062f46..2c02d882 100644 --- a/lmdb/error.go +++ b/lmdb/error.go @@ -70,6 +70,9 @@ const ( // lmdb.IsErrnoFn(err, os.IsPermission) type Errno C.int +// ErrNotFound is returned by Txn.Get and Cursor.Get +var ErrNotFound = &OpError{"lmdb", NotFound} + // minimum and maximum values produced for the Errno type. syscall.Errnos of // other values may still be produced. const minErrno, maxErrno C.int = C.MDB_KEYEXIST, C.MDB_LAST_ERRCODE @@ -87,7 +90,7 @@ func _operrno(op string, ret int) error { // not exist or if the Cursor reached the end of the database without locating // a value (EOF). func IsNotFound(err error) bool { - return IsErrno(err, NotFound) + return err == ErrNotFound } // IsNotExist returns true the path passed to the Env.Open method does not @@ -124,6 +127,9 @@ func IsErrnoFn(err error, fn func(error) bool) bool { if err == nil { return false } + if err == ErrNotFound { + return fn(NotFound) + } if err, ok := err.(*OpError); ok { return fn(err.Errno) } diff --git a/lmdb/txn.go b/lmdb/txn.go index 3814c79b..80b68c0d 100644 --- a/lmdb/txn.go +++ b/lmdb/txn.go @@ -279,6 +279,8 @@ func (txn *Txn) bytes(val *C.MDB_val) []byte { // returned by Get references a readonly section of memory that must not be // accessed after txn has terminated. // +// Returns ErrNotFound if the key is not present in dbi. +// // See mdb_get. func (txn *Txn) Get(dbi DBI, key []byte) ([]byte, error) { kdata, kn := valBytes(key) @@ -287,6 +289,12 @@ func (txn *Txn) Get(dbi DBI, key []byte) ([]byte, error) { unsafe.Pointer(&kdata[0]), C.size_t(kn), txn.val, ) + + if ret == C.MDB_NOTFOUND { + *txn.val = C.MDB_val{} + return nil, ErrNotFound + } + err := operrno("mdb_get", ret) if err != nil { *txn.val = C.MDB_val{} @@ -352,6 +360,8 @@ func (txn *Txn) PutReserve(dbi DBI, key []byte, n int, flags uint) ([]byte, erro // Del deletes an item from database dbi. Del ignores val unless dbi has the // DupSort flag. // +// Returns ErrNotFound if the key is not present in dbi. +// // See mdb_del. func (txn *Txn) Del(dbi DBI, key, val []byte) error { kdata, kn := valBytes(key) @@ -361,6 +371,11 @@ func (txn *Txn) Del(dbi DBI, key, val []byte) error { unsafe.Pointer(&kdata[0]), C.size_t(kn), unsafe.Pointer(&vdata[0]), C.size_t(vn), ) + + if ret == C.MDB_NOTFOUND { + return ErrNotFound + } + return operrno("mdb_del", ret) }