diff --git a/src/cursor.rs b/src/cursor.rs index 78908ce..e5bb29c 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -38,6 +38,11 @@ pub trait ReadCursor<'txn> : Cursor<'txn> { Ok((key_out, data_out)) } } + + /// Open a new read-only cursor on the given database. + fn iter<'t>(&'t mut self) -> Items<'t> { + Items::new(self) + } } pub trait WriteCursor<'txn> : ReadCursor<'txn> { @@ -183,15 +188,9 @@ pub struct Items<'txn> { impl <'txn> Items<'txn> { - /// Creates a new read-only cursor in the given database and transaction. Prefer using - /// `WriteTransaction::open_write_cursor()`. - pub fn new(txn: &'txn Transaction, db: Database) -> LmdbResult> { - let mut cursor: *mut MDB_cursor = ptr::null_mut(); - unsafe { - // Create the cursor - try!(lmdb_result(mdb_cursor_open(txn.txn(), db.dbi(), &mut cursor))); - } - Ok(Items { cursor: cursor, op: ffi::MDB_FIRST, next_op: ffi::MDB_NEXT }) + /// Creates a new iterator backed by the given cursor. + fn new<'t>(cursor: &Cursor<'t>) -> Items<'t> { + Items { cursor: cursor.cursor(), op: ffi::MDB_FIRST, next_op: ffi::MDB_NEXT } } } @@ -203,13 +202,13 @@ impl <'txn> Iterator<(&'txn [u8], &'txn [u8])> for Items<'txn> { unsafe { let err_code = ffi::mdb_cursor_get(self.cursor, &mut key, &mut data, self.op); - // Seek to the next item + // Set the operation for the next get self.op = self.next_op; if err_code == ffi::MDB_SUCCESS { Some((val_to_slice(key), val_to_slice(data))) } else { - // The documentation says that mdb_cursor_get may fail with MDB_NOTFOUND and MDB_EINVAL - // (and we shouldn't be passing in invalid parameters). + // The documentation for mdb_cursor_get specifies that it may fail with MDB_NOTFOUND + // and MDB_EINVAL (and we shouldn't be passing in invalid parameters). // TODO: validate that these are the only failures possible. debug_assert!(err_code == ffi::MDB_NOTFOUND, "Unexpected LMDB error {}.", LmdbError::from_err_code(err_code)); @@ -233,7 +232,7 @@ mod test { use transaction::*; #[test] - fn test_items() { + fn test_iter() { let dir = io::TempDir::new("test").unwrap(); let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); let db = env.open_db(None).unwrap(); @@ -247,7 +246,8 @@ mod test { } let txn = env.begin_read_txn().unwrap(); - let iter = txn.iter(db).unwrap(); + let mut cursor = txn.open_read_cursor(db).unwrap(); + let iter = cursor.iter(); let items: Vec<(&[u8], &[u8])> = iter.collect(); assert_eq!(vec!((b"key1", b"val1"), @@ -256,7 +256,6 @@ mod test { items); } - #[test] fn test_get() { let dir = io::TempDir::new("test").unwrap(); @@ -447,94 +446,92 @@ mod test { cursor.get(None, None, MDB_LAST).unwrap()); } - - #[bench] - fn bench_items(b: &mut Bencher) { - let n = 100u32; + fn get_bench_ctx<'a>(num_rows: u32) -> (io::TempDir, Environment) { let dir = io::TempDir::new("test").unwrap(); let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); - let db = env.open_db(None).unwrap(); { + let db = env.open_db(None).unwrap(); let mut txn = env.begin_write_txn().unwrap(); - for i in range(0, n) { + for i in range(0, num_rows) { txn.put(db, format!("key{}", i).as_bytes(), format!("val{}", i).as_bytes(), WriteFlags::empty()).unwrap(); } txn.commit().unwrap(); } + (dir, env) + } + /// Benchmark of iterator sequential read performance. + #[bench] + fn bench_seq_iter(b: &mut Bencher) { + let n = 100; + let (_dir, env) = get_bench_ctx(n); + let db = env.open_db(None).unwrap(); let txn = env.begin_read_txn().unwrap(); + b.iter(|| { + let mut cursor = txn.open_read_cursor(db).unwrap(); let mut i = 0; - for (key, data) in txn.iter(db).unwrap() { + let mut count = 0u32; + for (key, data) in cursor.iter() { i = i + key.len() + data.len(); + count = count + 1; } black_box(i); + assert_eq!(count, n); }); } + /// Benchmark of cursor sequential read performance. #[bench] - fn bench_cursor(b: &mut Bencher) { - let n = 100u32; - let dir = io::TempDir::new("test").unwrap(); - let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); + fn bench_seq_cursor(b: &mut Bencher) { + let n = 100; + let (_dir, env) = get_bench_ctx(n); let db = env.open_db(None).unwrap(); - - { - let mut txn = env.begin_write_txn().unwrap(); - for i in range(0, n) { - txn.put(db, format!("key{}", i).as_bytes(), format!("val{}", i).as_bytes(), WriteFlags::empty()).unwrap(); - } - txn.commit().unwrap(); - } - let txn = env.begin_read_txn().unwrap(); + b.iter(|| { let cursor = txn.open_read_cursor(db).unwrap(); let mut i = 0; + let mut count = 0u32; - let (key_opt, val) = cursor.get(None, None, MDB_FIRST).unwrap(); - i = i + key_opt.map(|key| key.len()).unwrap_or(0) + val.len(); - - for _ in range(1, n) { - let (key_opt, val) = cursor.get(None, None, MDB_NEXT).unwrap(); - i = i + key_opt.map(|key| key.len()).unwrap_or(0) + val.len(); + while let Ok((key_opt, val)) = cursor.get(None, None, MDB_NEXT) { + i += key_opt.map(|key| key.len()).unwrap_or(0) + val.len(); + count += 1; } + black_box(i); + assert_eq!(count, n); }); } + /// Benchmark of raw LMDB sequential read performance (control). #[bench] - fn bench_raw(b: &mut Bencher) { - let n = 100u32; - let dir = io::TempDir::new("test").unwrap(); - let env = Environment::new().open(dir.path(), io::USER_RWX).unwrap(); + fn bench_seq_raw(b: &mut Bencher) { + let n = 100; + let (_dir, env) = get_bench_ctx(n); let db = env.open_db(None).unwrap(); - { - let mut txn = env.begin_write_txn().unwrap(); - for i in range(0, n) { - txn.put(db, format!("key{}", i).as_bytes(), format!("val{}", i).as_bytes(), WriteFlags::empty()).unwrap(); - } - txn.commit().unwrap(); - } - let dbi: MDB_dbi = db.dbi(); let _txn = env.begin_read_txn().unwrap(); let txn = _txn.txn(); - let mut key = MDB_val { mv_size: 0, mv_data: ptr::null_mut() }; let mut data = MDB_val { mv_size: 0, mv_data: ptr::null_mut() }; let mut cursor: *mut MDB_cursor = ptr::null_mut(); b.iter(|| unsafe { - assert_eq!(mdb_cursor_open(txn, dbi, &mut cursor), 0); + mdb_cursor_open(txn, dbi, &mut cursor); let mut i = 0; + let mut count = 0u32; + while mdb_cursor_get(cursor, &mut key, &mut data, MDB_NEXT) == 0 { - i = i + key.mv_size + data.mv_size; + i += key.mv_size + data.mv_size; + count += 1; }; + black_box(i); + assert_eq!(count, n); mdb_cursor_close(cursor); }); } diff --git a/src/transaction.rs b/src/transaction.rs index 0c8d940..ef9d5b1 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -3,7 +3,7 @@ use std::{mem, ptr, raw}; use std::kinds::marker; use std::io::BufWriter; -use cursor::{RoCursor, RwCursor, Items}; +use cursor::{RoCursor, RwCursor}; use environment::Environment; use database::Database; use error::{LmdbResult, lmdb_result}; @@ -81,13 +81,6 @@ pub trait ReadTransaction<'env> : Transaction<'env> { RoCursor::new(self, db) } - /// Open a new read-only cursor on the given database. - fn iter<'txn>(&'txn self, db: Database) -> LmdbResult> { - Items::new(self, db) - } - - - /// Gets the option flags for the given database in the transaction. fn db_flags(&self, db: Database) -> LmdbResult { let mut flags: c_uint = 0;