Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace ElasticArray with SmallVec #282

Merged
merged 19 commits into from
Dec 19, 2019
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions kvdb-memorydb/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog].
## [Unreleased]
### Fixed
- `iter_from_prefix` behaviour synced with the `kvdb-rocksdb`

### Changed
- Default column support removed from the API
- Column argument type changed from `Option<u32>` to `u32`
Expand Down
4 changes: 2 additions & 2 deletions kvdb-memorydb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ impl KeyValueDB for InMemory {
match self.columns.read().get(&col) {
Some(map) => Box::new(
// TODO: worth optimizing at all?
map.clone().into_iter().map(|(k, v)| (k.into_boxed_slice(), v.into_vec().into_boxed_slice())),
map.clone().into_iter().map(|(k, v)| (k.into_boxed_slice(), v.into_boxed_slice())),
),
None => Box::new(None.into_iter()),
}
Expand All @@ -102,7 +102,7 @@ impl KeyValueDB for InMemory {
map.clone()
.into_iter()
.filter(move |&(ref k, _)| k.starts_with(prefix))
.map(|(k, v)| (k.into_boxed_slice(), v.into_vec().into_boxed_slice())),
.map(|(k, v)| (k.into_boxed_slice(), v.into_boxed_slice())),
),
None => Box::new(None.into_iter()),
}
Expand Down
1 change: 1 addition & 0 deletions kvdb-rocksdb/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog].
- `DatabaseConfig::default()` defaults to 1 column
- `Database::with_columns` still accepts `u32`, but panics if `0` is provided
- `Database::open` panics if configuration with 0 columns is provided
- [BREAKING] Remove `ElasticArray` and use the new `DBValue` (alias for `Vec<u8>`) and `DBKey` types from `kvdb`. (See [PR #282](https://github.com/paritytech/parity-common/pull/282/files))
dvdplm marked this conversation as resolved.
Show resolved Hide resolved

## [0.2.0] - 2019-11-28
- Switched away from using [parity-rocksdb](https://crates.io/crates/parity-rocksdb) in favour of upstream [rust-rocksdb](https://crates.io/crates/rocksdb) (see [PR #257](https://github.com/paritytech/parity-common/pull/257) for details)
Expand Down
2 changes: 1 addition & 1 deletion kvdb-rocksdb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ name = "bench_read_perf"
harness = false

[dependencies]
elastic-array = "0.10.2"
smallvec = "1.0.0"
fs-swap = "0.2.4"
interleaved-ordered = "0.1.1"
kvdb = { path = "../kvdb", version = "0.1" }
Expand Down
12 changes: 6 additions & 6 deletions kvdb-rocksdb/benches/bench_read_perf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ criterion_main!(benches);
/// family and default options. Needs manual cleanup.
fn open_db() -> Database {
let tempdir_str = "./benches/_rocksdb_bench_get";
let cfg = DatabaseConfig::with_columns(Some(1));
let cfg = DatabaseConfig::with_columns(1);
let db = Database::open(&cfg, tempdir_str).expect("rocksdb works");
db
}
Expand Down Expand Up @@ -81,7 +81,7 @@ fn populate(db: &Database) -> io::Result<Vec<H256>> {
}
}
// In ethereum keys are mostly 32 bytes and payloads ~140bytes.
batch.put(Some(0), &key.as_bytes(), &n_random_bytes(140));
batch.put(0, &key.as_bytes(), &n_random_bytes(140));
}
db.write(batch)?;
// Clear the overlay
Expand All @@ -106,7 +106,7 @@ fn get(c: &mut Criterion) {
for _ in 0..iterations {
// This has no measurable impact on performance (~30ns)
let needle = needles.choose(&mut rand::thread_rng()).expect("needles is not empty");
black_box(db.get(Some(0), needle.as_bytes()).unwrap());
black_box(db.get(0, needle.as_bytes()).unwrap());
}
elapsed = start.elapsed();
});
Expand Down Expand Up @@ -135,7 +135,7 @@ fn get(c: &mut Criterion) {
for _ in 0..iterations {
// This has no measurable impact on performance (~30ns)
let needle = needles.choose(&mut rand::thread_rng()).expect("needles is not empty");
black_box(db.get_by_prefix(Some(0), &needle.as_bytes()[..8]).unwrap());
black_box(db.get_by_prefix(0, &needle.as_bytes()[..8]).unwrap());
}
elapsed = start.elapsed();
});
Expand Down Expand Up @@ -166,7 +166,7 @@ fn iter(c: &mut Criterion) {
let (alloc_stats, _) = count_alloc(|| {
let start = Instant::now();
for _ in 0..iterations {
black_box(db.iter(Some(0)).take(1000).collect::<Vec<_>>());
black_box(db.iter(0).take(1000).collect::<Vec<_>>());
}
elapsed = start.elapsed();
});
Expand All @@ -193,7 +193,7 @@ fn iter(c: &mut Criterion) {
let (alloc_stats, _) = count_alloc(|| {
let start = Instant::now();
for _ in 0..iterations {
black_box(db.iter(Some(0)).next().unwrap());
black_box(db.iter(0).next().unwrap());
}
elapsed = start.elapsed();
});
Expand Down
13 changes: 6 additions & 7 deletions kvdb-rocksdb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ use rocksdb::{
};

use crate::iter::KeyValuePair;
use elastic_array::ElasticArray32;
use fs_swap::{swap, swap_nonatomic};
use interleaved_ordered::interleave_ordered;
use kvdb::{DBOp, DBTransaction, DBValue, KeyValueDB};
use kvdb::{DBKey, DBOp, DBTransaction, DBValue, KeyValueDB};
use log::{debug, warn};

#[cfg(target_os = "linux")]
Expand Down Expand Up @@ -245,9 +244,9 @@ pub struct Database {
read_opts: ReadOptions,
block_opts: BlockBasedOptions,
// Dirty values added with `write_buffered`. Cleaned on `flush`.
overlay: RwLock<Vec<HashMap<ElasticArray32<u8>, KeyState>>>,
overlay: RwLock<Vec<HashMap<DBKey, KeyState>>>,
// Values currently being flushed. Cleared when `flush` completes.
flushing: RwLock<Vec<HashMap<ElasticArray32<u8>, KeyState>>>,
flushing: RwLock<Vec<HashMap<DBKey, KeyState>>>,
// Prevents concurrent flushes.
// Value indicates if a flush is in progress.
flushing_lock: Mutex<bool>,
Expand Down Expand Up @@ -483,7 +482,7 @@ impl Database {
None => cfs
.db
.get_pinned_cf_opt(cfs.cf(col as usize), key, &self.read_opts)
.map(|r| r.map(|v| DBValue::from_slice(&v)))
.map(|r| r.map(|v| v.to_vec()))
.map_err(other_io_err),
}
}
Expand Down Expand Up @@ -511,7 +510,7 @@ impl Database {
.iter()
.filter_map(|(k, v)| match *v {
KeyState::Insert(ref value) => {
Some((k.clone().into_vec().into_boxed_slice(), value.clone().into_vec().into_boxed_slice()))
Some((k.clone().into_vec().into_boxed_slice(), value.clone().into_boxed_slice()))
}
KeyState::Delete => None,
})
Expand Down Expand Up @@ -892,7 +891,7 @@ mod tests {
batch.put(0, b"foo", b"baz");
db.write(batch).unwrap();

assert_eq!(db.get(0, b"foo").unwrap().unwrap().as_ref(), b"baz");
assert_eq!(db.get(0, b"foo").unwrap().unwrap(), b"baz");
}

#[test]
Expand Down
4 changes: 2 additions & 2 deletions kvdb-web/tests/indexed_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async fn reopen_the_database_with_more_columns() {
batch.put(0, b"hello", b"world");
db.write_buffered(batch);

assert_eq!(db.get(0, b"hello").unwrap().unwrap().as_ref(), b"world");
assert_eq!(db.get(0, b"hello").unwrap().unwrap(), b"world");

// Check the database version
assert_eq!(db.version(), 1);
Expand All @@ -51,7 +51,7 @@ async fn reopen_the_database_with_more_columns() {
let db = open_db(3).await;

// The value should still be present
assert_eq!(db.get(0, b"hello").unwrap().unwrap().as_ref(), b"world");
assert_eq!(db.get(0, b"hello").unwrap().unwrap(), b"world");
assert!(db.get(0, b"trash").unwrap().is_none());

// The version should be bumped
Expand Down
4 changes: 2 additions & 2 deletions kvdb/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ The format is based on [Keep a Changelog].

## [Unreleased]
### Changed
- Default column support removed from the API
- [BREAKING] Default column support removed from the API
dvdplm marked this conversation as resolved.
Show resolved Hide resolved
- Column argument type changed from `Option<u32>` to `u32`
- Migration `None` -> `0`, `Some(0)` -> `1`, `Some(1)` -> `2`, etc.

- [BREAKING] Remove `ElasticArray` and change `DBValue` to be a type alias for `Vec<u8>` and add a `DBKey` backed by a `SmallVec`. (See [PR #282](https://github.com/paritytech/parity-common/pull/282/files))
## [0.1.1] - 2019-10-24
### Dependencies
- Updated dependencies (https://github.com/paritytech/parity-common/pull/239)
Expand Down
2 changes: 1 addition & 1 deletion kvdb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ license = "GPL-3.0"
edition = "2018"

[dependencies]
elastic-array = "0.10.2"
smallvec = "1.0.0"
bytes = { package = "parity-bytes", version = "0.1", path = "../parity-bytes" }
22 changes: 9 additions & 13 deletions kvdb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//! Key-Value store abstraction with `RocksDB` backend.

use bytes::Bytes;
use elastic_array::{ElasticArray128, ElasticArray32};
use smallvec::SmallVec;
use std::io;
use std::path::Path;
use std::sync::Arc;
Expand All @@ -26,7 +26,9 @@ use std::sync::Arc;
pub const PREFIX_LEN: usize = 12;

/// Database value.
pub type DBValue = ElasticArray128<u8>;
pub type DBValue = Vec<u8>;
/// Database keys.
pub type DBKey = SmallVec<[u8; 32]>;
dvdplm marked this conversation as resolved.
Show resolved Hide resolved

/// Write transaction. Batches a sequence of put/delete operations for efficiency.
#[derive(Default, Clone, PartialEq)]
Expand All @@ -38,8 +40,8 @@ pub struct DBTransaction {
/// Database operation.
#[derive(Clone, PartialEq)]
pub enum DBOp {
Insert { col: u32, key: ElasticArray32<u8>, value: DBValue },
Delete { col: u32, key: ElasticArray32<u8> },
Insert { col: u32, key: DBKey, value: DBValue },
Delete { col: u32, key: DBKey },
}

impl DBOp {
Expand Down Expand Up @@ -73,23 +75,17 @@ impl DBTransaction {

/// Insert a key-value pair in the transaction. Any existing value will be overwritten upon write.
pub fn put(&mut self, col: u32, key: &[u8], value: &[u8]) {
let mut ekey = ElasticArray32::new();
ekey.append_slice(key);
self.ops.push(DBOp::Insert { col, key: ekey, value: DBValue::from_slice(value) });
self.ops.push(DBOp::Insert { col, key: DBKey::from_slice(key), value: value.to_vec() })
}

/// Insert a key-value pair in the transaction. Any existing value will be overwritten upon write.
pub fn put_vec(&mut self, col: u32, key: &[u8], value: Bytes) {
let mut ekey = ElasticArray32::new();
ekey.append_slice(key);
self.ops.push(DBOp::Insert { col, key: ekey, value: DBValue::from_vec(value) });
self.ops.push(DBOp::Insert { col, key: DBKey::from_slice(key), value });
}

/// Delete value by key.
pub fn delete(&mut self, col: u32, key: &[u8]) {
let mut ekey = ElasticArray32::new();
ekey.append_slice(key);
self.ops.push(DBOp::Delete { col, key: ekey });
self.ops.push(DBOp::Delete { col, key: DBKey::from_slice(key) });
}
}

Expand Down
1 change: 1 addition & 0 deletions parity-util-mem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog].
[Keep a Changelog]: http://keepachangelog.com/en/1.0.0/

## [Unreleased]
- [BREAKING] Remove `MallocSizeOf` impls for `ElasticArray` and implement it for `SmallVec` (32 and 36). (See [PR #282](https://github.com/paritytech/parity-common/pull/282/files))

## [0.2.1] - 2019-10-24
### Dependencies
Expand Down
4 changes: 2 additions & 2 deletions parity-util-mem/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ wee_alloc = { version = "0.4.5", optional = true }
mimallocator = { version = "0.1.3", features = ["secure"], optional = true }
mimalloc-sys = { version = "0.1.6", optional = true }

elastic-array = { version = "0.10.2", optional = true }
smallvec = { version = "1.0.0", optional = true }
ethereum-types = { version = "0.8.0", optional = true, path = "../ethereum-types" }
parking_lot = { version = "0.9.0", optional = true }

Expand All @@ -43,6 +43,6 @@ jemalloc-global = ["jemallocator"]
# use mimalloc as global allocator
mimalloc-global = ["mimallocator", "mimalloc-sys"]
# implement additional types
ethereum-impls = ["ethereum-types", "elastic-array", "parking_lot"]
ethereum-impls = ["ethereum-types", "parking_lot", "smallvec"]
# Full estimate: no call to allocator
estimate-heapsize = []
96 changes: 74 additions & 22 deletions parity-util-mem/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,15 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.

//! Implementation of `MallocSize` for common types :
//! - etheureum types uint and fixed hash.
//! - elastic_array arrays
//! - ethereum types uint and fixed hash.
//! - smallvec arrays of sizes 32, 36
//! - parking_lot mutex structures

use super::{MallocSizeOf, MallocSizeOfOps};
use elastic_array::{
ElasticArray1024, ElasticArray128, ElasticArray16, ElasticArray2, ElasticArray2048, ElasticArray256,
ElasticArray32, ElasticArray36, ElasticArray4, ElasticArray512, ElasticArray64, ElasticArray8,
};

use ethereum_types::{Bloom, H128, H160, H256, H264, H32, H512, H520, H64, U128, U256, U512, U64};
use parking_lot::{Mutex, RwLock};
use smallvec::SmallVec;

#[cfg(not(feature = "std"))]
use core as std;
Expand All @@ -36,31 +34,29 @@ malloc_size_of_is_0!(std::time::Duration);

malloc_size_of_is_0!(U64, U128, U256, U512, H32, H64, H128, H160, H256, H264, H512, H520, Bloom);

macro_rules! impl_elastic_array {
($name: ident, $dummy: ident, $size: expr) => {
impl<T> MallocSizeOf for $name<T>
macro_rules! impl_smallvec {
($size: expr) => {
impl<T> MallocSizeOf for SmallVec<[T; $size]>
where
T: MallocSizeOf,
{
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self[..].size_of(ops)
let mut n = if self.spilled() {
self.capacity() * core::mem::size_of::<T>()
dvdplm marked this conversation as resolved.
Show resolved Hide resolved
} else {
0
};
for elem in self.iter() {
n += elem.size_of(ops);
}
n
}
}
};
}

impl_elastic_array!(ElasticArray2, ElasticArray2Dummy, 2);
impl_elastic_array!(ElasticArray4, ElasticArray4Dummy, 4);
impl_elastic_array!(ElasticArray8, ElasticArray8Dummy, 8);
impl_elastic_array!(ElasticArray16, ElasticArray16Dummy, 16);
impl_elastic_array!(ElasticArray32, ElasticArray32Dummy, 32);
impl_elastic_array!(ElasticArray36, ElasticArray36Dummy, 36);
impl_elastic_array!(ElasticArray64, ElasticArray64Dummy, 64);
impl_elastic_array!(ElasticArray128, ElasticArray128Dummy, 128);
impl_elastic_array!(ElasticArray256, ElasticArray256Dummy, 256);
impl_elastic_array!(ElasticArray512, ElasticArray512Dummy, 512);
impl_elastic_array!(ElasticArray1024, ElasticArray1024Dummy, 1024);
impl_elastic_array!(ElasticArray2048, ElasticArray2048Dummy, 2048);
dvdplm marked this conversation as resolved.
Show resolved Hide resolved
impl_smallvec!(32); // kvdb uses this
impl_smallvec!(36); // trie-db uses this

impl<T: MallocSizeOf> MallocSizeOf for Mutex<T> {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
Expand All @@ -73,3 +69,59 @@ impl<T: MallocSizeOf> MallocSizeOf for RwLock<T> {
self.read().size_of(ops)
}
}

#[cfg(test)]
mod tests {
use crate::{allocators::new_malloc_size_ops, MallocSizeOf, MallocSizeOfOps};
use smallvec::SmallVec;
impl_smallvec!(3);

#[test]
fn test_smallvec_stack_allocated_type() {
let mut v: SmallVec<[u8; 3]> = SmallVec::new();
let mut ops = new_malloc_size_ops();
assert_eq!(v.size_of(&mut ops), 0);
v.push(1);
v.push(2);
v.push(3);
assert_eq!(v.size_of(&mut ops), 0);
assert!(!v.spilled());
v.push(4);
assert!(v.spilled(), "SmallVec spills when going beyond the capacity of the inner backing array");
assert_eq!(v.size_of(&mut ops), 4); // 4 u8s on the heap
}

#[test]
fn test_smallvec_boxed_stack_allocated_type() {
let mut v: SmallVec<[Box<u8>; 3]> = SmallVec::new();
let mut ops = new_malloc_size_ops();
assert_eq!(v.size_of(&mut ops), 0);
v.push(Box::new(1u8));
v.push(Box::new(2u8));
v.push(Box::new(3u8));
assert_eq!(v.size_of(&mut ops), 3); // 3 u8s on the heap, boxes are on the stack
dvdplm marked this conversation as resolved.
Show resolved Hide resolved
assert!(!v.spilled());
v.push(Box::new(4u8));
assert!(v.spilled(), "SmallVec spills when going beyond the capacity of the inner backing array");
let mut ops = new_malloc_size_ops();
assert_eq!(v.size_of(&mut ops), 36); // 4*8 (boxes) + 4 u8 in the heap
}

#[test]
fn test_smallvec_heap_allocated_type() {
let mut v: SmallVec<[String; 3]> = SmallVec::new();
let mut ops = new_malloc_size_ops();
assert_eq!(v.size_of(&mut ops), 0);
v.push("COW".into());
v.push("PIG".into());
v.push("DUCK".into());
assert!(!v.spilled());
assert_eq!(v.size_of(&mut ops), 10);
dvdplm marked this conversation as resolved.
Show resolved Hide resolved
v.push("ÖWL".into());
assert!(v.spilled());
let mut ops = new_malloc_size_ops();
// Not super clear where 110 comes from tbh, should be 14 bytes of data + 4 pointers = 14 + 32 = 46
// so the allocator is likely doing something interesting with Strings.
assert_eq!(v.size_of(&mut ops), 110);
}
}