bigint: improve from_str_radix performance

Before:
    test from_str_radix_02 ... bench:       8,432 ns/iter (+/- 280)
    test from_str_radix_08 ... bench:       7,397 ns/iter (+/- 95)
    test from_str_radix_10 ... bench:       7,344 ns/iter (+/- 142)
    test from_str_radix_16 ... bench:       6,753 ns/iter (+/- 157)
    test from_str_radix_36 ... bench:       7,093 ns/iter (+/- 60)

After:
    test from_str_radix_02 ... bench:       3,295 ns/iter (+/- 81)
    test from_str_radix_08 ... bench:       1,377 ns/iter (+/- 56)
    test from_str_radix_10 ... bench:       1,583 ns/iter (+/- 16)
    test from_str_radix_16 ... bench:       1,483 ns/iter (+/- 53)
    test from_str_radix_36 ... bench:       1,628 ns/iter (+/- 27)
This commit is contained in:
Josh Stone 2015-12-15 21:59:51 -08:00
parent 49529895a2
commit 22ff3f918d
1 changed files with 119 additions and 35 deletions

View File

@ -66,12 +66,11 @@ use std::iter::repeat;
use std::num::ParseIntError; use std::num::ParseIntError;
use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub}; use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Rem, Shl, Shr, Sub};
use std::str::{self, FromStr}; use std::str::{self, FromStr};
use std::{cmp, fmt, hash}; use std::{fmt, hash};
use std::cmp::Ordering::{self, Less, Greater, Equal}; use std::cmp::Ordering::{self, Less, Greater, Equal};
use std::{i64, u64}; use std::{i64, u64};
use rand::Rng; use rand::Rng;
use rustc_serialize::hex::ToHex;
use traits::{ToPrimitive, FromPrimitive}; use traits::{ToPrimitive, FromPrimitive};
@ -254,41 +253,122 @@ impl FromStr for BigUint {
} }
} }
// Read bitwise digits that evenly divide BigDigit
fn from_bitwise_digits_le(v: &[u8], bits: usize) -> BigUint {
debug_assert!(!v.is_empty() && bits <= 8 && big_digit::BITS % bits == 0);
debug_assert!(v.iter().all(|&c| (c as BigDigit) < (1 << bits)));
let digits_per_big_digit = big_digit::BITS / bits;
let data = v.chunks(digits_per_big_digit).map(|chunk| {
chunk.iter().rev().fold(0u32, |acc, &c| (acc << bits) | c as BigDigit)
}).collect();
BigUint::new(data)
}
// Read bitwise digits that don't evenly divide BigDigit
fn from_inexact_bitwise_digits_le(v: &[u8], bits: usize) -> BigUint {
debug_assert!(!v.is_empty() && bits <= 8 && big_digit::BITS % bits != 0);
debug_assert!(v.iter().all(|&c| (c as BigDigit) < (1 << bits)));
let big_digits = (v.len() * bits + big_digit::BITS - 1) / big_digit::BITS;
let mut data = Vec::with_capacity(big_digits);
let mut d = 0;
let mut dbits = 0;
for &c in v {
d |= (c as DoubleBigDigit) << dbits;
dbits += bits;
if dbits >= big_digit::BITS {
let (hi, lo) = big_digit::from_doublebigdigit(d);
data.push(lo);
d = hi as DoubleBigDigit;
dbits -= big_digit::BITS;
}
}
if dbits > 0 {
debug_assert!(dbits < big_digit::BITS);
data.push(d as BigDigit);
}
BigUint::new(data)
}
// Read little-endian radix digits
fn from_radix_digits_be(v: &[u8], radix: u32) -> BigUint {
debug_assert!(!v.is_empty() && !radix.is_power_of_two());
debug_assert!(v.iter().all(|&c| (c as u32) < radix));
let (base, power) = get_radix_base(radix);
debug_assert!(base < (1 << 32));
let base = base as BigDigit;
let r = v.len() % power;
let i = if r == 0 { power } else { r };
let (head, tail) = v.split_at(i);
let first = head.iter().fold(0, |acc, &d| acc * radix + d as BigDigit);
let mut data = vec![first];
debug_assert!(tail.len() % power == 0);
for chunk in tail.chunks(power) {
let mut carry = 0;
data.push(0);
for d in data.iter_mut() {
*d = mac_with_carry(0, *d, base, &mut carry);
}
debug_assert!(carry == 0);
let n = chunk.iter().fold(0, |acc, &d| acc * radix + d as BigDigit);
add2(&mut data, &[n]);
if let Some(&0) = data.last() {
data.pop();
}
}
BigUint::new(data)
}
impl Num for BigUint { impl Num for BigUint {
type FromStrRadixErr = ParseBigIntError; type FromStrRadixErr = ParseBigIntError;
/// Creates and initializes a `BigUint`. /// Creates and initializes a `BigUint`.
#[inline]
fn from_str_radix(s: &str, radix: u32) -> Result<BigUint, ParseBigIntError> { fn from_str_radix(s: &str, radix: u32) -> Result<BigUint, ParseBigIntError> {
let (base, unit_len) = get_radix_base(radix); assert!(2 <= radix && radix <= 36, "The radix must be within 2...36");
let base_num = match base.to_biguint() { if s.is_empty() {
Some(base_num) => base_num, // create ParseIntError
None => { return Err(ParseBigIntError::Other); } try!(u64::from_str_radix(s, radix));
}; unreachable!();
}
let mut end = s.len(); // First normalize all characters to plain digit values
let mut n: BigUint = Zero::zero(); let mut v = Vec::with_capacity(s.len());
let mut power: BigUint = One::one(); for (i, c) in s.chars().enumerate() {
loop { if let Some(d) = c.to_digit(radix) {
let start = cmp::max(end, unit_len) - unit_len; v.push(d as u8);
let d = try!(usize::from_str_radix(&s[start .. end], radix)); } else {
let d: Option<BigUint> = FromPrimitive::from_usize(d); // create ParseIntError
match d { try!(u64::from_str_radix(&s[i..], radix));
Some(d) => { unreachable!();
// FIXME(#5992): assignment operator overloads
// n += d * &power;
n = n + d * &power;
} }
None => { return Err(ParseBigIntError::Other); }
} }
if end <= unit_len {
return Ok(n); let res = if radix.is_power_of_two() {
} // Powers of two can use bitwise masks and shifting instead of multiplication
end -= unit_len; let bits = radix.trailing_zeros() as usize;
// FIXME(#5992): assignment operator overloads v.reverse();
// power *= &base_num; if big_digit::BITS % bits == 0 {
power = power * &base_num; from_bitwise_digits_le(&v, bits)
} else {
from_inexact_bitwise_digits_le(&v, bits)
} }
} else {
from_radix_digits_be(&v, radix)
};
Ok(res)
} }
} }
@ -1351,7 +1431,9 @@ impl BigUint {
if bytes.is_empty() { if bytes.is_empty() {
Zero::zero() Zero::zero()
} else { } else {
BigUint::parse_bytes(bytes.to_hex().as_bytes(), 16).unwrap() let mut v = bytes.to_vec();
v.reverse();
BigUint::from_bytes_le(&*v)
} }
} }
@ -1360,9 +1442,11 @@ impl BigUint {
/// The bytes are in little-endian byte order. /// The bytes are in little-endian byte order.
#[inline] #[inline]
pub fn from_bytes_le(bytes: &[u8]) -> BigUint { pub fn from_bytes_le(bytes: &[u8]) -> BigUint {
let mut v = bytes.to_vec(); if bytes.is_empty() {
v.reverse(); Zero::zero()
BigUint::from_bytes_be(&*v) } else {
from_bitwise_digits_le(bytes, 8)
}
} }
/// Returns the byte representation of the `BigUint` in little-endian byte order. /// Returns the byte representation of the `BigUint` in little-endian byte order.