Skip to content

Commit

Permalink
refactor: integrate parallel implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
krushimir committed Nov 22, 2024
1 parent 3f52ef3 commit c9b4682
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 79 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ name = "store"
harness = false

[features]
concurrent = ["dep:rayon"]
default = ["std", "concurrent"]
executable = ["dep:clap", "dep:rand-utils", "std"]
serde = ["dep:serde", "serde?/alloc", "winter-math/serde"]
concurrent = ["dep:rayon"]
std = [
"blake3/std",
"dep:cc",
Expand All @@ -67,12 +67,12 @@ num-complex = { version = "0.4", default-features = false }
rand = { version = "0.8", default-features = false }
rand_core = { version = "0.6", default-features = false }
rand-utils = { version = "0.10", package = "winter-rand-utils", optional = true }
rayon = { version = "1.10", optional = true }
serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] }
sha3 = { version = "0.10", default-features = false }
winter-crypto = { version = "0.10", default-features = false }
winter-math = { version = "0.10", default-features = false }
winter-utils = { version = "0.10", default-features = false }
rayon = { version = "1.10.0", optional = true }

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
Expand Down
10 changes: 3 additions & 7 deletions benches/parallel-subtree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,10 @@ fn smt_parallel_subtree(c: &mut Criterion) {
(key, value)
})
.collect();

let control = Smt::with_entries(entries.clone()).unwrap();
(entries, control)
entries
},
|(entries, control)| {
// Benchmarked function.
let tree = Smt::with_entries_par(hint::black_box(entries)).unwrap();
assert_eq!(tree.root(), control.root());
|entries| {
Smt::with_entries(hint::black_box(entries)).unwrap()
},
BatchSize::SmallInput,
);
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ pub fn parallel_construction(
println!("Running a parallel construction benchmark:");
let now = Instant::now();

let tree = Smt::with_entries_par(entries).unwrap();
let tree = Smt::with_entries(entries).unwrap();

let elapsed = now.elapsed();
println!(
Expand Down
72 changes: 41 additions & 31 deletions src/merkle/smt/full/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,40 +78,50 @@ impl Smt {
pub fn with_entries(
entries: impl IntoIterator<Item = (RpoDigest, Word)>,
) -> Result<Self, MerkleError> {
// create an empty tree
let mut tree = Self::new();

// This being a sparse data structure, the EMPTY_WORD is not assigned to the `BTreeMap`, so
// entries with the empty value need additional tracking.
let mut key_set_to_zero = BTreeSet::new();

for (key, value) in entries {
let old_value = tree.insert(key, value);

if old_value != EMPTY_WORD || key_set_to_zero.contains(&key) {
return Err(MerkleError::DuplicateValuesForIndex(
LeafIndex::<SMT_DEPTH>::from(key).value(),
));
#[cfg(feature="concurrent")]
{
let mut seen_keys = BTreeSet::new();
let entries: Vec<_> = entries
.into_iter()
.map(|(key, value)| {
if !seen_keys.insert(key) {
Err(MerkleError::DuplicateValuesForIndex(
LeafIndex::<SMT_DEPTH>::from(key).value(),
))
} else {
Ok((key, value))
}
})
.collect::<Result<_, _>>()?;
if entries.is_empty() {
return Ok(Self::default());
}

if value == EMPTY_WORD {
key_set_to_zero.insert(key);
};
<Self as SparseMerkleTree<SMT_DEPTH>>::with_entries_par(entries)
}
Ok(tree)
}
#[cfg(not(feature="concurrent"))]
{
// create an empty tree
let mut tree = Self::new();

// This being a sparse data structure, the EMPTY_WORD is not assigned to the `BTreeMap`, so
// entries with the empty value need additional tracking.
let mut key_set_to_zero = BTreeSet::new();

for (key, value) in entries {
let old_value = tree.insert(key, value);

if old_value != EMPTY_WORD || key_set_to_zero.contains(&key) {
return Err(MerkleError::DuplicateValuesForIndex(
LeafIndex::<SMT_DEPTH>::from(key).value(),
));
}

/// The parallel version of [`Smt::with_entries()`].
///
/// Returns a new [`Smt`] instantiated with leaves set as specified by the provided entries,
/// constructed in parallel.
///
/// All leaves omitted from the entries list are set to [Self::EMPTY_VALUE].
#[cfg(feature = "concurrent")]
pub fn with_entries_par(
entries: impl IntoIterator<Item = (RpoDigest, Word)>,
) -> Result<Self, MerkleError> {
<Self as SparseMerkleTree<SMT_DEPTH>>::with_entries_par(Vec::from_iter(entries))
if value == EMPTY_WORD {
key_set_to_zero.insert(key);
};
}
Ok(tree)
}
}

/// Returns a new [`Smt`] instantiated from already computed leaves and nodes.
Expand Down
80 changes: 45 additions & 35 deletions src/merkle/smt/simple/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,50 +71,60 @@ impl<const DEPTH: u8> SimpleSmt<DEPTH> {
pub fn with_leaves(
entries: impl IntoIterator<Item = (u64, Word)>,
) -> Result<Self, MerkleError> {
// create an empty tree
let mut tree = Self::new()?;

// compute the max number of entries. We use an upper bound of depth 63 because we consider
// passing in a vector of size 2^64 infeasible.
let max_num_entries = 2_usize.pow(DEPTH.min(63).into());

// This being a sparse data structure, the EMPTY_WORD is not assigned to the `BTreeMap`, so
// entries with the empty value need additional tracking.
let mut key_set_to_zero = BTreeSet::new();

for (idx, (key, value)) in entries.into_iter().enumerate() {
if idx >= max_num_entries {
return Err(MerkleError::InvalidNumEntries(max_num_entries));
#[cfg(feature = "concurrent")]
{
let mut seen_keys = BTreeSet::new();
let entries: Vec<_> = entries
.into_iter()
.map(|(col, value)| {
if col >= max_num_entries as u64 {
Err(MerkleError::InvalidIndex {
depth: DEPTH,
value: col,
})
} else if !seen_keys.insert(col) {
Err(MerkleError::DuplicateValuesForIndex(col))
} else {
Ok((LeafIndex::<DEPTH>::new(col).unwrap(), value))
}
})
.collect::<Result<_, _>>()?;

if entries.is_empty() {
return Self::new();
}
<Self as SparseMerkleTree<DEPTH>>::with_entries_par(entries)
}
#[cfg(not(feature = "concurrent"))]
{
// create an empty tree
let mut tree = Self::new()?;

let old_value = tree.insert(LeafIndex::<DEPTH>::new(key)?, value);
// This being a sparse data structure, the EMPTY_WORD is not assigned to the `BTreeMap`, so
// entries with the empty value need additional tracking.
let mut key_set_to_zero = BTreeSet::new();

if old_value != Self::EMPTY_VALUE || key_set_to_zero.contains(&key) {
return Err(MerkleError::DuplicateValuesForIndex(key));
}
for (idx, (key, value)) in entries.into_iter().enumerate() {
if idx >= max_num_entries {
return Err(MerkleError::InvalidNumEntries(max_num_entries));
}

if value == Self::EMPTY_VALUE {
key_set_to_zero.insert(key);
};
}
Ok(tree)
}
let old_value = tree.insert(LeafIndex::<DEPTH>::new(key)?, value);

/// The parallel version of [`SimpleSmt::with_leaves()`].
///
/// Returns a new [`SimpleSmt`] instantiated with leaves set as specified by the provided
/// entries.
///
/// All leaves omitted from the entries list are set to [ZERO; 4].
#[cfg(feature = "concurrent")]
pub fn with_leaves_par(
entries: impl IntoIterator<Item = (u64, Word)>,
) -> Result<Self, MerkleError> {
let entries: Vec<_> = entries
.into_iter()
.map(|(col, value)| (LeafIndex::<DEPTH>::new(col).unwrap(), value))
.collect();
<Self as SparseMerkleTree<DEPTH>>::with_entries_par(entries)
if old_value != Self::EMPTY_VALUE || key_set_to_zero.contains(&key) {
return Err(MerkleError::DuplicateValuesForIndex(key));
}

if value == Self::EMPTY_VALUE {
key_set_to_zero.insert(key);
};
}
Ok(tree)
}
}

/// Returns a new [`SimpleSmt`] instantiated from already computed leaves and nodes.
Expand Down
14 changes: 11 additions & 3 deletions src/merkle/smt/simple/tests.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#[cfg(not(feature="concurrent"))]
use alloc::vec::Vec;

use super::{
Expand Down Expand Up @@ -86,16 +87,17 @@ fn build_sparse_tree() {
#[test]
fn build_contiguous_tree() {
let tree_with_leaves =
SimpleSmt::<2>::with_leaves([0, 1, 2, 3].into_iter().zip(digests_to_words(&VALUES4)))
SimpleSmt::<8>::with_leaves([0, 1, 2, 3].into_iter().zip(digests_to_words(&VALUES4)))
.unwrap();

let tree_with_contiguous_leaves =
SimpleSmt::<2>::with_contiguous_leaves(digests_to_words(&VALUES4)).unwrap();
SimpleSmt::<8>::with_contiguous_leaves(digests_to_words(&VALUES4)).unwrap();

assert_eq!(tree_with_leaves, tree_with_contiguous_leaves);
}

#[test]
#[cfg(not(feature="concurrent"))]
fn test_depth2_tree() {
let tree =
SimpleSmt::<2>::with_leaves(KEYS4.into_iter().zip(digests_to_words(&VALUES4))).unwrap();
Expand All @@ -120,6 +122,7 @@ fn test_depth2_tree() {
}

#[test]
#[cfg(not(feature="concurrent"))]
fn test_inner_node_iterator() -> Result<(), MerkleError> {
let tree =
SimpleSmt::<2>::with_leaves(KEYS4.into_iter().zip(digests_to_words(&VALUES4))).unwrap();
Expand Down Expand Up @@ -151,6 +154,7 @@ fn test_inner_node_iterator() -> Result<(), MerkleError> {
}

#[test]
#[cfg(not(feature="concurrent"))]
fn test_insert() {
const DEPTH: u8 = 3;
let mut tree =
Expand Down Expand Up @@ -193,6 +197,7 @@ fn test_insert() {
}

#[test]
#[cfg(not(feature="concurrent"))]
fn small_tree_opening_is_consistent() {
// ____k____
// / \
Expand Down Expand Up @@ -314,6 +319,7 @@ fn test_simplesmt_with_leaves_nonexisting_leaf() {
}

#[test]
#[cfg(not(feature="concurrent"))]
fn test_simplesmt_set_subtree() {
// Final Tree:
//
Expand Down Expand Up @@ -369,6 +375,7 @@ fn test_simplesmt_set_subtree() {

/// Ensures that an invalid input node index into `set_subtree()` incurs no mutation of the tree
#[test]
#[cfg(not(feature="concurrent"))]
fn test_simplesmt_set_subtree_unchanged_for_wrong_index() {
// Final Tree:
//
Expand Down Expand Up @@ -410,6 +417,7 @@ fn test_simplesmt_set_subtree_unchanged_for_wrong_index() {

/// We insert an empty subtree that has the same depth as the original tree
#[test]
#[cfg(not(feature="concurrent"))]
fn test_simplesmt_set_subtree_entire_tree() {
// Initial Tree:
//
Expand Down Expand Up @@ -463,7 +471,7 @@ fn test_simplesmt_check_empty_root_constant() {

// HELPER FUNCTIONS
// --------------------------------------------------------------------------------------------

#[cfg(not(feature="concurrent"))]
fn compute_internal_nodes() -> (RpoDigest, RpoDigest, RpoDigest) {
let node2 = Rpo256::merge(&[VALUES4[0], VALUES4[1]]);
let node3 = Rpo256::merge(&[VALUES4[2], VALUES4[3]]);
Expand Down
1 change: 1 addition & 0 deletions src/merkle/store/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ fn test_get_invalid_node() {
}

#[test]
#[cfg(not(feature="concurrent"))]
fn test_add_sparse_merkle_tree_one_level() -> Result<(), MerkleError> {
let keys2: [u64; 2] = [0, 1];
let leaves2: [Word; 2] = [int_to_leaf(1), int_to_leaf(2)];
Expand Down

0 comments on commit c9b4682

Please sign in to comment.