Replace slow hex decoding function with optimized version

Fixes #207.
This commit is contained in:
Sebastian Geisler 2019-01-03 17:24:18 -08:00
parent 5a5158e120
commit c6a41651ab
1 changed files with 49 additions and 19 deletions

View File

@ -17,29 +17,59 @@
//! Various utility functions //! Various utility functions
use blockdata::opcodes; use blockdata::opcodes;
use util::iter::Pairable;
use consensus::encode; use consensus::encode;
/// Helper function to convert hex nibble characters to their respective value
#[inline]
fn hex_val(c: u8) -> Result<u8, encode::Error> {
let res = match c {
b'0' ... b'9' => c - '0' as u8,
b'a' ... b'f' => c - 'a' as u8 + 10,
b'A' ... b'F' => c - 'A' as u8 + 10,
_ => return Err(encode::Error::UnexpectedHexDigit(c as char)),
};
Ok(res)
}
/// Convert a hexadecimal-encoded string to its corresponding bytes /// Convert a hexadecimal-encoded string to its corresponding bytes
pub fn hex_bytes(s: &str) -> Result<Vec<u8>, encode::Error> { pub fn hex_bytes(data: &str) -> Result<Vec<u8>, encode::Error> {
let mut v = vec![]; // This code is optimized to be as fast as possible without using unsafe or platform specific
let mut iter = s.chars().pair(); // features. If you want to refactor it please make sure you don't introduce performance
// Do the parsing // regressions (see benches/from_hex.rs).
iter.by_ref().fold(Ok(()), |e, (f, s)|
if e.is_err() { e } // If the hex string has an uneven length fail early
else { if data.len() % 2 != 0 {
match (f.to_digit(16), s.to_digit(16)) { return Err(encode::Error::ParseFailed("hexstring of odd length"));
(None, _) => Err(encode::Error::UnexpectedHexDigit(f)),
(_, None) => Err(encode::Error::UnexpectedHexDigit(s)),
(Some(f), Some(s)) => { v.push((f * 0x10 + s) as u8); Ok(()) }
}
}
)?;
// Check that there was no remainder
match iter.remainder() {
Some(_) => Err(encode::Error::ParseFailed("hexstring of odd length")),
None => Ok(v)
} }
// Preallocate the uninitialized memory for the byte array
let mut res = Vec::with_capacity(data.len() / 2);
let mut hex_it = data.bytes();
loop {
// Get most significant nibble of current byte or end iteration
let msn = match hex_it.next() {
None => break,
Some(x) => x,
};
// Get least significant nibble of current byte
let lsn = match hex_it.next() {
None => unreachable!("len % 2 == 0"),
Some(x) => x,
};
// Convert bytes representing characters to their represented value and combine lsn and msn.
// The and_then and map are crucial for performance, in comparision to using ? and then
// using the results of that for the calculation it's nearly twice as fast. Using bit
// shifting and or instead of multiply and add on the other hand doesn't show a significant
// increase in performance.
match hex_val(msn).and_then(|msn_val| hex_val(lsn).map(|lsn_val| msn_val * 16 + lsn_val)) {
Ok(x) => res.push(x),
Err(e) => return Err(e),
}
}
Ok(res)
} }
/// Search for `needle` in the vector `haystack` and remove every /// Search for `needle` in the vector `haystack` and remove every