Skip to content

Commit e2c9849

Browse files
committed
tr: optimize parsing of taptrees
This gets our benchmarks to roughly where we were before we flattened the data structure. The `_tr_oneleaf_` benchmarks appear to be measurably faster than before but everything else is pretty-much the same. Since the old code was definitely slower -- using Arcs, making tons of tiny allocations, storing internal nodes -- we can infer that actual construction of TapTrees is just not a meaningful portion of the time taken to parse expressions, and further optimization effort should go to the expression parser.
1 parent 552e844 commit e2c9849

File tree

2 files changed

+42
-35
lines changed

2 files changed

+42
-35
lines changed

src/descriptor/tr/mod.rs

+4-35
Original file line numberDiff line numberDiff line change
@@ -377,38 +377,6 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Tr<Pk> {
377377
fn from_tree(root: expression::TreeIterItem) -> Result<Self, Error> {
378378
use crate::expression::{Parens, ParseTreeError};
379379

380-
struct TreeStack<'s, Pk: MiniscriptKey> {
381-
inner: Vec<(expression::TreeIterItem<'s>, TapTree<Pk>)>,
382-
}
383-
384-
impl<'s, Pk: MiniscriptKey> TreeStack<'s, Pk> {
385-
fn new() -> Self { Self { inner: Vec::with_capacity(128) } }
386-
387-
fn push(
388-
&mut self,
389-
parent: expression::TreeIterItem<'s>,
390-
tree: TapTree<Pk>,
391-
) -> Result<(), Error> {
392-
let mut next_push = (parent, tree);
393-
while let Some(top) = self.inner.pop() {
394-
if next_push.0.index() == top.0.index() {
395-
next_push.0 = top.0.parent().unwrap();
396-
next_push.1 = TapTree::combine(top.1, next_push.1)?;
397-
} else {
398-
self.inner.push(top);
399-
break;
400-
}
401-
}
402-
self.inner.push(next_push);
403-
Ok(())
404-
}
405-
406-
fn pop_final(&mut self) -> Option<TapTree<Pk>> {
407-
assert_eq!(self.inner.len(), 1);
408-
self.inner.pop().map(|x| x.1)
409-
}
410-
}
411-
412380
root.verify_toplevel("tr", 1..=2)
413381
.map_err(From::from)
414382
.map_err(Error::Parse)?;
@@ -425,7 +393,7 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Tr<Pk> {
425393
Some(tree) => tree,
426394
};
427395

428-
let mut tree_stack = TreeStack::new();
396+
let mut tree_builder = taptree::TapTreeBuilder::new();
429397
let mut tap_tree_iter = tap_tree.pre_order_iter();
430398
// while let construction needed because we modify the iterator inside the loop
431399
// (by calling skip_descendants to skip over the contents of the tapscripts).
@@ -440,18 +408,19 @@ impl<Pk: FromStrKey> crate::expression::FromTree for Tr<Pk> {
440408
node.verify_n_children("taptree branch", 2..=2)
441409
.map_err(From::from)
442410
.map_err(Error::Parse)?;
411+
tree_builder.push_inner_node()?;
443412
} else {
444413
let script = Miniscript::from_tree(node)?;
445414
// FIXME hack for https://github.com/rust-bitcoin/rust-miniscript/issues/734
446415
if script.ty.corr.base != crate::miniscript::types::Base::B {
447416
return Err(Error::NonTopLevel(format!("{:?}", script)));
448417
};
449418

450-
tree_stack.push(node.parent().unwrap(), TapTree::leaf(script))?;
419+
tree_builder.push_leaf(script);
451420
tap_tree_iter.skip_descendants();
452421
}
453422
}
454-
Tr::new(internal_key, tree_stack.pop_final())
423+
Tr::new(internal_key, Some(tree_builder.finalize()))
455424
}
456425
}
457426

src/descriptor/tr/taptree.rs

+38
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,41 @@ impl<Pk: ToPublicKey> TapTreeIterItem<'_, Pk> {
211211
TapLeafHash::from_script(&self.compute_script(), self.leaf_version())
212212
}
213213
}
214+
215+
pub(super) struct TapTreeBuilder<Pk: MiniscriptKey> {
216+
depths_leaves: Vec<(u8, Arc<Miniscript<Pk, Tap>>)>,
217+
complete_heights: u128, // ArrayVec<bool, 128> represented as a bitmap...
218+
current_height: u8, // ...and a height
219+
}
220+
221+
impl<Pk: MiniscriptKey> TapTreeBuilder<Pk> {
222+
pub(super) fn new() -> Self {
223+
Self { depths_leaves: vec![], complete_heights: 0, current_height: 0 }
224+
}
225+
226+
#[inline]
227+
pub(super) fn push_inner_node(&mut self) -> Result<(), TapTreeDepthError> {
228+
self.current_height += 1;
229+
if usize::from(self.current_height) >= TAPROOT_CONTROL_MAX_NODE_COUNT {
230+
return Err(TapTreeDepthError);
231+
}
232+
Ok(())
233+
}
234+
235+
#[inline]
236+
pub(super) fn push_leaf<A: Into<Arc<Miniscript<Pk, Tap>>>>(&mut self, ms: A) {
237+
self.depths_leaves.push((self.current_height, ms.into()));
238+
239+
while self.current_height > 0 {
240+
if self.complete_heights & (1 << self.current_height) == 0 {
241+
self.complete_heights |= 1 << self.current_height;
242+
break;
243+
}
244+
self.complete_heights &= !(1 << self.current_height);
245+
self.current_height -= 1;
246+
}
247+
}
248+
249+
#[inline]
250+
pub(super) fn finalize(self) -> TapTree<Pk> { TapTree { depths_leaves: self.depths_leaves } }
251+
}

0 commit comments

Comments
 (0)