docs: update changelog and readme
@@ -4,7 +4,9 @@ * Implemented the `PartialMmr` data structure (#195).
* Updated Winterfell dependency to v0.7 (#200) * Implemented RPX hash function (#201). * Added `FeltRng` and `RpoRandomCoin` (#237). +* Accelerated RPO/RPX hash functions using AVX512 instructions (#234). * Added `inner_nodes()` method to `PartialMmr` (#238). +* Improved `PartialMmr::apply_delta()` (#242). ## 0.7.1 (2023-10-10)
@@ -46,8 +46,14 @@ Both of these features imply the use of [alloc](https://doc.rust-lang.org/alloc/) to support heap-allocated collections.
To compile with `no_std`, disable default features via `--no-default-features` flag. +### AVX2 acceleration +On platforms with [AVX2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions) support, RPO and RPX hash function can be accelerated by using the vector processing unit. To enable AVX2 acceleration, the code needs to be compiled with the `avx2` target feature enabled. For example: +```shell +RUSTFLAGS="-C target-feature=+avx2" cargo build --release +``` + ### SVE acceleration -On platforms with [SVE](https://en.wikipedia.org/wiki/AArch64#Scalable_Vector_Extension_(SVE)) support, RPO hash function can be accelerated by using the vector processing unit. To enable SVE acceleration, the code needs to be compiled with the `sve` feature enabled. This feature has an effect only if the platform exposes `target-feature=sve` flag. On some platforms (e.g., Graviton 3), for this flag to be set, the compilation must be done in "native" mode. For example, to enable SVE acceleration on Graviton 3, we can execute the following: +On platforms with [SVE](https://en.wikipedia.org/wiki/AArch64#Scalable_Vector_Extension_(SVE)) support, RPO and RPX hash function can be accelerated by using the vector processing unit. To enable SVE acceleration, the code needs to be compiled with the `sve` feature enabled. This feature has an effect only if the platform exposes `target-feature=sve` flag. On some platforms (e.g., Graviton 3), for this flag to be set, the compilation must be done in "native" mode. For example, to enable SVE acceleration on Graviton 3, we can execute the following: ```shell RUSTFLAGS="-C target-cpu=native" cargo build --release --features sve ```
@@ -22,6 +22,7 @@ | Apple M1 Pro | 76 ns | 245 ns | 1.5 µs | 9.1 µs | 5.2 µs | 2.7 µs |
| Apple M2 Max | 71 ns | 233 ns | 1.3 µs | 7.9 µs | 4.6 µs | 2.4 µs | | Amazon Graviton 3 | 108 ns | | | | 5.3 µs | 3.1 µs | | AMD Ryzen 9 5950X | 64 ns | 273 ns | 1.2 µs | 9.1 µs | 5.5 µs | | +| AMD EPYC 9R14 | 83 ns | | | | 4.3 µs | 2.4 µs | | Intel Core i5-8279U | 68 ns | 536 ns | 2.0 µs | 13.6 µs | 8.5 µs | 4.4 µs | | Intel Xeon 8375C | 67 ns | | | | 8.2 µs | |@@ -33,11 +34,13 @@ | Apple M1 Pro | 1.0 µs | 1.5 µs | 19.4 µs | 118 µs | 69 µs | 35 µs |
| Apple M2 Max | 0.9 µs | 1.5 µs | 17.4 µs | 103 µs | 60 µs | 31 µs | | Amazon Graviton 3 | 1.4 µs | | | | 69 µs | 41 µs | | AMD Ryzen 9 5950X | 0.8 µs | 1.7 µs | 15.7 µs | 120 µs | 72 µs | | +| AMD EPYC 9R14 | 0.9 µs | | | | 56 µs | 32 µs | | Intel Core i5-8279U | 0.9 µs | | | | 107 µs | 56 µs | | Intel Xeon 8375C | 0.8 µs | | | | 110 µs | | Notes: - On Graviton 3, RPO256 and RPX256 are run with SVE acceleration enabled. +- On AMD EPYC 9R14, RPO256 and RPX256 are run with AVX2 acceleration enabled. ### Instructions Before you can run the benchmarks, you'll need to make sure you have Rust [installed](https://www.rust-lang.org/tools/install). After that, to run the benchmarks for RPO and BLAKE3, clone the current repository, and from the root directory of the repo run the following:
@@ -32,7 +32,6 @@ }
fn rpo256_sequential(c: &mut Criterion) { let v: [Felt; 100] = (0..100) - .into_iter() .map(Felt::new) .collect::<Vec<Felt>>() .try_into()@@ -45,7 +44,6 @@ c.bench_function("RPO256 sequential hashing (random)", |bench| {
bench.iter_batched( || { let v: [Felt; 100] = (0..100) - .into_iter() .map(|_| Felt::new(rand_value())) .collect::<Vec<Felt>>() .try_into()@@ -80,7 +78,6 @@ }
fn rpx256_sequential(c: &mut Criterion) { let v: [Felt; 100] = (0..100) - .into_iter() .map(Felt::new) .collect::<Vec<Felt>>() .try_into()@@ -93,7 +90,6 @@ c.bench_function("RPX256 sequential hashing (random)", |bench| {
bench.iter_batched( || { let v: [Felt; 100] = (0..100) - .into_iter() .map(|_| Felt::new(rand_value())) .collect::<Vec<Felt>>() .try_into()@@ -129,7 +125,6 @@ }
fn blake3_sequential(c: &mut Criterion) { let v: [Felt; 100] = (0..100) - .into_iter() .map(Felt::new) .collect::<Vec<Felt>>() .try_into()@@ -142,7 +137,6 @@ c.bench_function("Blake3 sequential hashing (random)", |bench| {
bench.iter_batched( || { let v: [Felt; 100] = (0..100) - .into_iter() .map(|_| Felt::new(rand_value())) .collect::<Vec<Felt>>() .try_into()
@@ -27,7 +27,7 @@ let leaf = generate_word(&mut seed);
// benchmarks - let mut insert = c.benchmark_group(format!("smt update_leaf")); + let mut insert = c.benchmark_group("smt update_leaf".to_string()); for (tree, count) in trees.iter_mut() { let depth = tree.depth();@@ -45,7 +45,7 @@ }
insert.finish(); - let mut path = c.benchmark_group(format!("smt get_leaf_path")); + let mut path = c.benchmark_group("smt get_leaf_path".to_string()); for (tree, count) in trees.iter_mut() { let depth = tree.depth();
@@ -15,7 +15,7 @@ }
/// Generates a random `Word`. fn random_word() -> Word { - rand_array::<Felt, 4>().into() + rand_array::<Felt, 4>() } /// Generates an index at the specified depth in `0..range`.
@@ -209,7 +209,7 @@ //
// Which have leaf nodes 20, 22, 23, 32 and 33. Hence overall we will have 5 paths -- one path // for each leaf. - let leaves = vec![NODE20, NODE22, NODE23, NODE32, NODE33]; + let leaves = [NODE20, NODE22, NODE23, NODE32, NODE33]; let expected_paths: Vec<(NodeIndex, ValuePath)> = leaves .iter() .map(|&leaf| {@@ -257,7 +257,7 @@ let value20 = mt.get_node(NODE20).unwrap();
let value32 = mt.get_node(NODE32).unwrap(); let value33 = mt.get_node(NODE33).unwrap(); - let leaves = vec![(NODE11, value11), (NODE20, value20), (NODE32, value32), (NODE33, value33)]; + let leaves = [(NODE11, value11), (NODE20, value20), (NODE32, value32), (NODE33, value33)]; let expected_leaves = leaves.iter().copied(); assert!(expected_leaves.eq(pmt.leaves()));
@@ -35,7 +35,7 @@ #[test]
fn build_empty_tree() { // tree of depth 3 let smt = SimpleSmt::new(3).unwrap(); - let mt = MerkleTree::new(ZERO_VALUES8.to_vec()).unwrap(); + let mt = MerkleTree::new(ZERO_VALUES8).unwrap(); assert_eq!(mt.root(), smt.root()); }@@ -74,14 +74,12 @@
/// Tests that [`SimpleSmt::with_contiguous_leaves`] works as expected #[test] fn build_contiguous_tree() { - let tree_with_leaves = SimpleSmt::with_leaves( - 2, - [0, 1, 2, 3].into_iter().zip(digests_to_words(&VALUES4).into_iter()), - ) - .unwrap(); + let tree_with_leaves = + SimpleSmt::with_leaves(2, [0, 1, 2, 3].into_iter().zip(digests_to_words(&VALUES4))) + .unwrap(); let tree_with_contiguous_leaves = - SimpleSmt::with_contiguous_leaves(2, digests_to_words(&VALUES4).into_iter()).unwrap(); + SimpleSmt::with_contiguous_leaves(2, digests_to_words(&VALUES4)).unwrap(); assert_eq!(tree_with_leaves, tree_with_contiguous_leaves); }@@ -89,8 +87,7 @@
#[test] fn test_depth2_tree() { let tree = - SimpleSmt::with_leaves(2, KEYS4.into_iter().zip(digests_to_words(&VALUES4).into_iter())) - .unwrap(); + SimpleSmt::with_leaves(2, KEYS4.into_iter().zip(digests_to_words(&VALUES4))).unwrap(); // check internal structure let (root, node2, node3) = compute_internal_nodes();@@ -118,8 +115,7 @@
#[test] fn test_inner_node_iterator() -> Result<(), MerkleError> { let tree = - SimpleSmt::with_leaves(2, KEYS4.into_iter().zip(digests_to_words(&VALUES4).into_iter())) - .unwrap(); + SimpleSmt::with_leaves(2, KEYS4.into_iter().zip(digests_to_words(&VALUES4))).unwrap(); // check depth 2 assert_eq!(VALUES4[0], tree.get_node(NodeIndex::make(2, 0)).unwrap());@@ -150,8 +146,7 @@
#[test] fn update_leaf() { let mut tree = - SimpleSmt::with_leaves(3, KEYS8.into_iter().zip(digests_to_words(&VALUES8).into_iter())) - .unwrap(); + SimpleSmt::with_leaves(3, KEYS8.into_iter().zip(digests_to_words(&VALUES8))).unwrap(); // update one value let key = 3;
@@ -210,7 +210,7 @@ #[test]
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)]; - let smt = SimpleSmt::with_leaves(1, keys2.into_iter().zip(leaves2.into_iter())).unwrap(); + let smt = SimpleSmt::with_leaves(1, keys2.into_iter().zip(leaves2)).unwrap(); let store = MerkleStore::from(&smt); let idx = NodeIndex::make(1, 0);@@ -228,7 +228,7 @@ #[test]
fn test_sparse_merkle_tree() -> Result<(), MerkleError> { let smt = SimpleSmt::with_leaves( SimpleSmt::MAX_DEPTH, - KEYS4.into_iter().zip(digests_to_words(&VALUES4).into_iter()), + KEYS4.into_iter().zip(digests_to_words(&VALUES4)), ) .unwrap();@@ -553,11 +553,8 @@ assert_eq!(mtree.get_path(index)?, value_path.path);
} let depth = 32; - let smt = SimpleSmt::with_leaves( - depth, - KEYS4.into_iter().zip(digests_to_words(&VALUES4).into_iter()), - ) - .unwrap(); + let smt = + SimpleSmt::with_leaves(depth, KEYS4.into_iter().zip(digests_to_words(&VALUES4))).unwrap(); let store = MerkleStore::from(&smt); let depth = smt.depth();