diff --git a/Cargo.toml b/Cargo.toml index 9948c1081..8febb1b3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,10 +96,21 @@ opt-level = 0 # codegen-units = 256 # rpath = false +## We use harness = false on these so that the divan reports are output on stdout. + [[bench]] name = "sync_atomic" harness = false +[[bench]] +name = "db_leveldb" +harness = false + +[[bench]] +name = "db_dbtvec" +harness = false + + [patch.crates-io] # 15a708cf6fcbd9ed3e65e5e2067be6e622176328 is tip of branch: make_storage_async as of 2024-03-18 tasm-lib = { git = "https://github.com/dan-da/tasm-lib.git", rev = "15a708cf6fcbd9ed3e65e5e2067be6e622176328" } diff --git a/benches/db_dbtvec.rs b/benches/db_dbtvec.rs new file mode 100644 index 000000000..726adc9c1 --- /dev/null +++ b/benches/db_dbtvec.rs @@ -0,0 +1,350 @@ +use divan::Bencher; +use leveldb::options::{Options, ReadOptions, WriteOptions}; +use leveldb_sys::Compression; +use neptune_core::database::storage::storage_schema::{traits::*, DbtVec, SimpleRustyStorage}; +use neptune_core::database::storage::storage_vec::traits::*; +use neptune_core::database::NeptuneLevelDb; + +// These database bench tests are made with divan. +// +// See: +// https://nikolaivazquez.com/blog/divan/ +// https://docs.rs/divan/0.1.0/divan/attr.bench.html +// https://github.com/nvzqz/divan +// +// Options for #[bench] attr: +// https://docs.rs/divan/0.1.0/divan/attr.bench.html#options +// +// name, crate, consts, types, sample_count, sample_size, threads +// counters, min_time, max_time, skip_ext_time, ignore + +fn main() { + divan::main(); +} + +/// These settings affect DB performance and correctness. +/// +/// Adjust and re-run the benchmarks to see effects. +/// +/// Rust docs: (basic) +/// https://docs.rs/rs-leveldb/0.1.5/leveldb/database/options/struct.Options.html +/// +/// C++ docs: (complete) +/// https://github.com/google/leveldb/blob/068d5ee1a3ac40dabd00d211d5013af44be55bea/include/leveldb/options.h +fn db_options() -> Option { + Some(Options { + // default: false + create_if_missing: true, + + // default: false + error_if_exists: true, + + // default: false + paranoid_checks: false, + + // default: None --> (4 * 1024 * 1024) + write_buffer_size: None, + + // default: None --> 1000 + max_open_files: None, + + // default: None --> 4 * 1024 + block_size: None, + + // default: None --> 16 + block_restart_interval: None, + + // default: Compression::No + // or: Compression::Snappy + compression: Compression::No, + + // default: None --> 8MB + cache: None, + // cache: Some(Cache::new(1024)), + // note: tests put 128 bytes in each entry. + // 100 entries = 12,800 bytes. + // So Cache of 1024 bytes is 8% of total data set. + // that seems reasonably realistic to get some + // hits/misses. + }) +} + +fn value() -> Vec { + (0..127).collect() +} + +async fn create_test_dbtvec() -> (SimpleRustyStorage, DbtVec>) { + let db = NeptuneLevelDb::open_new_test_database( + true, + db_options(), + Some(ReadOptions { + verify_checksums: false, + fill_cache: false, + }), + Some(WriteOptions { sync: true }), + ) + .await + .unwrap(); + let mut storage = SimpleRustyStorage::new(db); + let vec = storage.schema.new_vec::>("test-vector").await; + (storage, vec) +} + +mod write_100_entries { + use super::*; + + // note: numbers > 100 make the sync_on_write::put() test really slow. + const NUM_WRITE_ITEMS: u64 = 100; + + mod push { + use super::*; + + fn push_impl(bencher: Bencher, persist: bool) { + let rt = tokio::runtime::Runtime::new().unwrap(); + let (mut storage, mut vector) = rt.block_on(create_test_dbtvec()); + + bencher.bench_local(|| { + rt.block_on(async { + for _i in 0..NUM_WRITE_ITEMS { + vector.push(value()).await; + } + if persist { + storage.persist().await; + } + }); + }); + } + + #[divan::bench] + fn push(bencher: Bencher) { + push_impl(bencher, false); + } + + #[divan::bench] + fn push_and_persist(bencher: Bencher) { + push_impl(bencher, true); + } + } + + mod set { + use super::*; + + fn set_impl(bencher: Bencher, persist: bool) { + let rt = tokio::runtime::Runtime::new().unwrap(); + let (mut storage, mut vector) = rt.block_on(create_test_dbtvec()); + + for _i in 0..NUM_WRITE_ITEMS { + rt.block_on(vector.push(value())); + } + + bencher.bench_local(|| { + rt.block_on(async { + for i in 0..NUM_WRITE_ITEMS { + vector.set(i, value()).await; + } + + if persist { + storage.persist().await; + } + }); + }); + } + + #[divan::bench] + fn set(bencher: Bencher) { + set_impl(bencher, false); + } + + #[divan::bench] + fn set_and_persist(bencher: Bencher) { + set_impl(bencher, true); + } + } + + mod set_many { + use super::*; + + fn set_many_impl(bencher: Bencher, persist: bool) { + let rt = tokio::runtime::Runtime::new().unwrap(); + let (mut storage, mut vector) = rt.block_on(create_test_dbtvec()); + + for _ in 0..NUM_WRITE_ITEMS { + rt.block_on(vector.push(vec![42])); + } + + bencher.bench_local(|| { + rt.block_on(async { + let values: Vec<_> = (0..NUM_WRITE_ITEMS).map(|i| (i, value())).collect(); + vector.set_many(values).await; + if persist { + storage.persist().await + } + }); + }); + } + + #[divan::bench] + fn set_many(bencher: Bencher) { + set_many_impl(bencher, false); + } + + #[divan::bench] + fn set_many_and_persist(bencher: Bencher) { + set_many_impl(bencher, true); + } + } + + mod pop { + use super::*; + + fn pop_impl(bencher: Bencher, persist: bool) { + let rt = tokio::runtime::Runtime::new().unwrap(); + let (mut storage, mut vector) = rt.block_on(create_test_dbtvec()); + + for _i in 0..NUM_WRITE_ITEMS { + rt.block_on(vector.push(value())); + } + + bencher.bench_local(|| { + rt.block_on(async { + for _i in 0..NUM_WRITE_ITEMS { + vector.pop().await; + } + + if persist { + storage.persist().await; + } + }); + }); + } + + #[divan::bench] + fn pop(bencher: Bencher) { + pop_impl(bencher, false); + } + + #[divan::bench] + fn pop_and_persist(bencher: Bencher) { + pop_impl(bencher, true); + } + } +} + +mod read_100_entries { + use super::*; + + const NUM_READ_ITEMS: u64 = 100; + + fn get_impl(bencher: Bencher, num_each: usize, persisted: bool) { + let rt = tokio::runtime::Runtime::new().unwrap(); + let vector = rt.block_on(async { + let (mut storage, mut vector) = create_test_dbtvec().await; + + for _i in 0..NUM_READ_ITEMS { + vector.push(value()).await; + } + if persisted { + storage.persist().await; + } + vector + }); + + bencher.bench_local(|| { + rt.block_on(async { + for i in 0..NUM_READ_ITEMS { + for _j in 0..num_each { + let _ = vector.get(i).await; + } + } + }); + }); + } + + fn get_many_impl(bencher: Bencher, num_each: usize, persisted: bool) { + let rt = tokio::runtime::Runtime::new().unwrap(); + let vector = rt.block_on(async { + let (mut storage, mut vector) = create_test_dbtvec().await; + + for _i in 0..NUM_READ_ITEMS { + vector.push(value()).await; + } + if persisted { + storage.persist().await; + } + vector + }); + + let indices: Vec = (0..NUM_READ_ITEMS).collect(); + bencher.bench_local(|| { + rt.block_on(async { + for _j in 0..num_each { + let _ = vector.get_many(&indices).await; + } + }); + }); + } + + mod get_each_entry_1_time { + use super::*; + + mod persisted { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + get_impl(bencher, 1, true); + } + + #[divan::bench] + fn get_many(bencher: Bencher) { + get_many_impl(bencher, 1, true); + } + } + + mod unpersisted { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + get_impl(bencher, 1, false); + } + + #[divan::bench] + fn get_many(bencher: Bencher) { + get_many_impl(bencher, 1, false); + } + } + } + + mod get_each_entry_20_times { + use super::*; + + mod persisted { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + get_impl(bencher, 20, true); + } + + #[divan::bench] + fn get_many(bencher: Bencher) { + get_many_impl(bencher, 20, true); + } + } + + mod unpersisted { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + get_impl(bencher, 20, false); + } + + #[divan::bench] + fn get_many(bencher: Bencher) { + get_many_impl(bencher, 20, false); + } + } + } +} diff --git a/benches/db_leveldb.rs b/benches/db_leveldb.rs new file mode 100644 index 000000000..96ead340c --- /dev/null +++ b/benches/db_leveldb.rs @@ -0,0 +1,442 @@ +use divan::Bencher; +use leveldb::batch::WriteBatch; +use leveldb::options::{Options, ReadOptions, WriteOptions}; +use leveldb_sys::Compression; +use neptune_core::database::leveldb::DB; + +// These database bench tests are made with divan. +// +// See: +// https://nikolaivazquez.com/blog/divan/ +// https://docs.rs/divan/0.1.0/divan/attr.bench.html +// https://github.com/nvzqz/divan +// +// Options for #[bench] attr: +// https://docs.rs/divan/0.1.0/divan/attr.bench.html#options +// +// name, crate, consts, types, sample_count, sample_size, threads +// counters, min_time, max_time, skip_ext_time, ignore + +fn main() { + divan::main(); +} + +/// These settings affect DB performance and correctness. +/// +/// Important: the default settings are not optimal, +/// eg: no read cache. +/// +/// Adjust and re-run the benchmarks to see effects. +/// +/// Rust docs: (basic) +/// https://docs.rs/rs-leveldb/0.1.5/leveldb/database/options/struct.Options.html +/// +/// C++ docs: (complete) +/// https://github.com/google/leveldb/blob/068d5ee1a3ac40dabd00d211d5013af44be55bea/include/leveldb/options.h +fn db_options() -> Option { + Some(Options { + // default: false + create_if_missing: true, + + // default: false + error_if_exists: true, + + // default: false + paranoid_checks: false, + + // default: None --> (4 * 1024 * 1024) + write_buffer_size: None, + + // default: None --> 1000 + max_open_files: None, + + // default: None --> 4 * 1024 + block_size: None, + + // default: None --> 16 + block_restart_interval: None, + + // default: Compression::No + // or: Compression::Snappy + compression: Compression::No, + + // default: None --> 8MB + // cache: None, + cache: None, + // note: tests put 128 bytes in each entry. + // 100 entries = 12,800 bytes. + // + // Warning: WriteBatch.put() tends to crash + // when this value is Some(Cache::new(..)) + // instead of None. + }) +} + +fn read_options(verify_checksums: bool, fill_cache: bool) -> Option { + Some(ReadOptions { + verify_checksums, + fill_cache, + }) +} +fn read_options_default() -> Option { + Some(ReadOptions::new()) +} + +fn write_options(sync: bool) -> Option { + Some(WriteOptions { sync }) +} + +fn value() -> Vec { + (0..127).collect() +} + +mod write_100_entries { + use super::*; + + // note: numbers > 100 make the sync_on_write::put() test really slow. + const NUM_WRITE_ITEMS: u32 = 100; + + mod puts { + use super::*; + + fn put(bencher: Bencher, sync: bool) { + let mut db = DB::open_new_test_database( + true, + db_options(), + read_options_default(), + write_options(sync), + ) + .unwrap(); + + bencher.bench_local(|| { + for i in 0..NUM_WRITE_ITEMS { + let _ = db.put(&i, &value()); + } + }); + } + + fn batch_put(bencher: Bencher, sync: bool) { + let mut db = DB::open_new_test_database( + true, + db_options(), + read_options_default(), + write_options(sync), + ) + .unwrap(); + + bencher.bench_local(|| { + let wb = WriteBatch::new(); + for i in 0..NUM_WRITE_ITEMS { + wb.put(&i, &value()); + } + let _ = db.write(&wb, sync); + }); + } + + fn batch_put_write(bencher: Bencher, sync: bool) { + let mut db = DB::open_new_test_database( + true, + db_options(), + read_options_default(), + write_options(sync), + ) + .unwrap(); + + let wb = WriteBatch::new(); + for i in 0..NUM_WRITE_ITEMS { + wb.put(&i, &value()); + } + + bencher.bench_local(|| { + let _ = db.write(&wb, sync); + }); + } + + mod sync_on_write { + use super::*; + + #[divan::bench] + fn put(bencher: Bencher) { + super::put(bencher, true); + } + + #[divan::bench] + fn batch_put(bencher: Bencher) { + super::batch_put(bencher, true); + } + + #[divan::bench] + fn batch_put_write(bencher: Bencher) { + super::batch_put_write(bencher, true); + } + } + + mod no_sync_on_write { + use super::*; + + #[divan::bench] + fn put(bencher: Bencher) { + super::put(bencher, false); + } + + #[divan::bench] + fn batch_put(bencher: Bencher) { + super::batch_put(bencher, false); + } + + #[divan::bench] + fn batch_put_write(bencher: Bencher) { + super::batch_put_write(bencher, false); + } + } + } + + mod deletes { + use super::*; + + fn delete(bencher: Bencher, sync: bool) { + let mut db = DB::open_new_test_database( + true, + db_options(), + read_options_default(), + write_options(sync), + ) + .unwrap(); + + for i in 0..NUM_WRITE_ITEMS { + let _ = db.put(&i, &value()); + } + + bencher.bench_local(|| { + for i in 0..NUM_WRITE_ITEMS { + let _ = db.delete(&i); + } + }); + } + + fn batch_delete(bencher: Bencher, sync: bool) { + let mut db = DB::open_new_test_database( + true, + db_options(), + read_options_default(), + write_options(sync), + ) + .unwrap(); + + // batch write items, unsync + let wb = WriteBatch::new(); + for i in 0..NUM_WRITE_ITEMS { + wb.put(&i, &value()); + } + let _ = db.write(&wb, false); + + // batch delete items, sync + let wb_del = WriteBatch::new(); + + bencher.bench_local(|| { + for i in 0..NUM_WRITE_ITEMS { + wb.delete(&i); + } + let _ = db.write(&wb_del, sync); + }); + } + + fn batch_delete_write(bencher: Bencher, sync: bool) { + let mut db = DB::open_new_test_database( + true, + db_options(), + read_options_default(), + write_options(sync), + ) + .unwrap(); + + // batch write items, unsync + let wb = WriteBatch::new(); + for i in 0..NUM_WRITE_ITEMS { + wb.put(&i, &value()); + } + let _ = db.write(&wb, false); + + // batch delete items, sync + let wb_del = WriteBatch::new(); + for i in 0..NUM_WRITE_ITEMS { + wb.delete(&i); + } + + bencher.bench_local(|| { + let _ = db.write(&wb_del, sync); + }); + } + + mod sync_on_write { + use super::*; + + #[divan::bench] + fn delete(bencher: Bencher) { + super::delete(bencher, true); + } + + #[divan::bench] + fn batch_delete(bencher: Bencher) { + super::batch_delete(bencher, true); + } + + #[divan::bench] + fn batch_delete_write(bencher: Bencher) { + super::batch_delete_write(bencher, true); + } + } + + mod no_sync_on_write { + use super::*; + + #[divan::bench] + fn delete(bencher: Bencher) { + super::delete(bencher, false); + } + + #[divan::bench] + fn batch_delete(bencher: Bencher) { + super::batch_delete(bencher, false); + } + + #[divan::bench] + fn batch_delete_write(bencher: Bencher) { + super::batch_delete_write(bencher, false); + } + } + } +} + +mod read_100_entries { + use super::*; + + const NUM_READ_ITEMS: u32 = 100; + + mod gets { + use super::*; + + fn get(bencher: Bencher, num_reads: usize, cache: bool, verify_checksum: bool) { + let mut db = DB::open_new_test_database( + true, + db_options(), + read_options(verify_checksum, cache), + write_options(false), + ) + .unwrap(); + + for i in 0..NUM_READ_ITEMS { + let _ = db.put(&i, &value()); + } + + bencher.bench_local(|| { + for i in 0..NUM_READ_ITEMS { + for _j in 0..num_reads { + let _ = db.get(&i); + } + } + }); + } + + mod get_each_entry_1_time { + use super::*; + + mod fill_cache { + use super::*; + + mod verify_checksums { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + super::get(bencher, 1, true, true); + } + } + + mod no_verify_checksums { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + super::get(bencher, 1, true, false); + } + } + } + mod no_fill_cache { + use super::*; + + mod verify_checksums { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + super::get(bencher, 1, false, true); + } + } + + mod no_verify_checksums { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + super::get(bencher, 1, false, false); + } + } + } + } + + mod get_each_entry_20_times { + use super::*; + + mod fill_cache { + use super::*; + + mod verify_checksums { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + super::get(bencher, 20, true, true); + } + } + + mod no_verify_checksums { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + super::get(bencher, 20, true, false); + } + } + } + mod no_fill_cache { + use super::*; + + mod verify_checksums { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + super::get(bencher, 20, false, true); + } + } + + mod no_verify_checksums { + use super::*; + + #[divan::bench] + fn get(bencher: Bencher) { + super::get(bencher, 20, false, false); + } + } + } + } + } +} + +mod storage_schema { + + mod dbtvec {} +} + +mod storage_vec {} diff --git a/src/database/mod.rs b/src/database/mod.rs index 01787e475..80b74b6d0 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,4 +1,4 @@ -mod leveldb; +pub mod leveldb; mod neptune_leveldb; pub mod storage; diff --git a/src/database/neptune_leveldb.rs b/src/database/neptune_leveldb.rs index f21e5df8e..0bedf4475 100644 --- a/src/database/neptune_leveldb.rs +++ b/src/database/neptune_leveldb.rs @@ -288,7 +288,6 @@ where } } -#[cfg(any(test, doctest))] impl NeptuneLevelDb where Key: Serialize + DeserializeOwned + Send + Sync + 'static,