Merge rust-bitcoin/rust-bitcoin#2759: Bench `base58` encoding and remove `SmallVec` to improve perf
4646690521
fix clippy lint by using resize instead of push (Riccardo Casatta)deeb160b86
remove SmallVec (Riccardo Casatta)e4b707ba83
add bench for base58::encode_check (Riccardo Casatta) Pull request description: In a downstream app I've seen printing a descriptor is not a cheap operation, analyzing the flamegraph it seems base58 encoding of the xpub is the culprit  This PR adds benches for the `encode_check` function, and add the changes gaining more boost, which is also good cause it removes code. Other attempts didn't provide enough benefit for inclusion but I report them here for knowledge. ``` ## baseline running 2 tests test benches::bench_encode_check_50 ... bench: 8,760 ns/iter (+/- 113) test benches::bench_encode_check_xpub ... bench: 19,982 ns/iter (+/- 109) ## remove smallvec running 2 tests test benches::bench_encode_check_50 ... bench: 7,935 ns/iter (+/- 129) test benches::bench_encode_check_xpub ... bench: 18,076 ns/iter (+/- 184) ## increase smallvec to 128 (fits xpub) test benches::bench_encode_check_50 ... bench: 8,786 ns/iter (+/- 738) test benches::bench_encode_check_xpub ... bench: 20,006 ns/iter (+/- 2,611) ## avoid char-to-str by keeping str map test benches::bench_encode_check_50 ... bench: 7,895 ns/iter (+/- 88) test benches::bench_encode_check_xpub ... bench: 17,883 ns/iter (+/- 118) ``` Gains are good (~10%), but I don't think they explains the 3ms to print a descriptor in wasm env, probably is the sha256 for the checksum is fast in cargo bench but slow in wasm env, but I didn't research on the topic. ACKs for top commit: apoelstra: ACK4646690521
tcharding: ACK4646690521
Tree-SHA512: 978bbe22c99bb455028d90532d59a076321e0c19105fc8335bd44cd84fbedda109083380df5c658b85121242c88d442994cfc58d141f3fc5daa66c27b1499329
This commit is contained in:
commit
89d6991cf1
|
@ -19,6 +19,9 @@
|
|||
#[macro_use]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(bench)]
|
||||
extern crate test;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
extern crate std;
|
||||
|
||||
|
@ -28,7 +31,7 @@ pub mod error;
|
|||
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub use alloc::{string::String, vec::Vec};
|
||||
use core::{fmt, iter, slice, str};
|
||||
use core::{fmt, str};
|
||||
#[cfg(feature = "std")]
|
||||
pub use std::{string::String, vec::Vec};
|
||||
|
||||
|
@ -115,7 +118,9 @@ pub fn decode_check(data: &str) -> Result<Vec<u8>, Error> {
|
|||
}
|
||||
|
||||
/// Encodes `data` as a base58 string (see also `base58::encode_check()`).
|
||||
pub fn encode(data: &[u8]) -> String { encode_iter(data.iter().cloned()) }
|
||||
pub fn encode(data: &[u8]) -> String {
|
||||
encode_iter(data.iter().cloned())
|
||||
}
|
||||
|
||||
/// Encodes `data` as a base58 string including the checksum.
|
||||
///
|
||||
|
@ -148,7 +153,7 @@ where
|
|||
I: Iterator<Item = u8> + Clone,
|
||||
W: fmt::Write,
|
||||
{
|
||||
let mut ret = SmallVec::new();
|
||||
let mut ret = Vec::with_capacity(128);
|
||||
|
||||
let mut leading_zero_count = 0;
|
||||
let mut leading_zeroes = true;
|
||||
|
@ -173,9 +178,7 @@ where
|
|||
}
|
||||
|
||||
// ... then reverse it and convert to chars
|
||||
for _ in 0..leading_zero_count {
|
||||
ret.push(0);
|
||||
}
|
||||
ret.resize(ret.len() + leading_zero_count, 0);
|
||||
|
||||
for ch in ret.iter().rev() {
|
||||
writer.write_char(BASE58_CHARS[*ch as usize] as char)?;
|
||||
|
@ -184,37 +187,6 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Vector-like object that holds the first 100 elements on the stack. If more space is needed it
|
||||
/// will be allocated on the heap.
|
||||
struct SmallVec<T> {
|
||||
len: usize,
|
||||
stack: [T; 100],
|
||||
heap: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T: Default + Copy> SmallVec<T> {
|
||||
fn new() -> SmallVec<T> { SmallVec { len: 0, stack: [T::default(); 100], heap: Vec::new() } }
|
||||
|
||||
fn push(&mut self, val: T) {
|
||||
if self.len < 100 {
|
||||
self.stack[self.len] = val;
|
||||
self.len += 1;
|
||||
} else {
|
||||
self.heap.push(val);
|
||||
}
|
||||
}
|
||||
|
||||
fn iter(&self) -> iter::Chain<slice::Iter<T>, slice::Iter<T>> {
|
||||
// If len<100 then we just append an empty vec
|
||||
self.stack[0..self.len].iter().chain(self.heap.iter())
|
||||
}
|
||||
|
||||
fn iter_mut(&mut self) -> iter::Chain<slice::IterMut<T>, slice::IterMut<T>> {
|
||||
// If len<100 then we just append an empty vec
|
||||
self.stack[0..self.len].iter_mut().chain(self.heap.iter_mut())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use hex::test_hex_unwrap as hex;
|
||||
|
@ -284,3 +256,28 @@ mod tests {
|
|||
assert_eq!(decode_check(&encode(&[1, 2, 3])), Err(TooShortError { length: 3 }.into()));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(bench)]
|
||||
mod benches {
|
||||
use test::{black_box, Bencher};
|
||||
|
||||
#[bench]
|
||||
pub fn bench_encode_check_50(bh: &mut Bencher) {
|
||||
let data: alloc::vec::Vec<_> = (0u8..50).collect();
|
||||
|
||||
bh.iter(|| {
|
||||
let r = super::encode_check(&data);
|
||||
black_box(&r);
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
pub fn bench_encode_check_xpub(bh: &mut Bencher) {
|
||||
let data: alloc::vec::Vec<_> = (0u8..78).collect(); // lenght of xpub
|
||||
|
||||
bh.iter(|| {
|
||||
let r = super::encode_check(&data);
|
||||
black_box(&r);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue