Skip to content

Commit

Permalink
Kill DoubleBigDigit; BigDigit := usize
Browse files Browse the repository at this point in the history
Reimplement the low level arithmetic functions without DoubleBigDigit, and then
change BigDigit to usize. This ends up being a pretty significant performance
win, even with my relatively simple and dumb code - an assembly version could be
much faster (x86 at least can implement all these operations with one or just a
few instructions).

Before:
test divide_0      ... bench:       3,906 ns/iter (+/- 125)
test divide_1      ... bench:     293,600 ns/iter (+/- 6,545)
test divide_2      ... bench: 657,792,410 ns/iter (+/- 28,285,336)
test factorial_100 ... bench:       6,538 ns/iter (+/- 971)
test fib_100       ... bench:       1,431 ns/iter (+/- 39)
test multiply_0    ... bench:         419 ns/iter (+/- 6)
test multiply_1    ... bench:      34,637 ns/iter (+/- 550)
test multiply_2    ... bench:   4,523,752 ns/iter (+/- 1,039,485)
test shr           ... bench:         535 ns/iter (+/- 13)
test to_string     ... bench:         621 ns/iter (+/- 58)

After:
test divide_0      ... bench:       3,138 ns/iter (+/- 136)
test divide_1      ... bench:     123,971 ns/iter (+/- 2,817)
test divide_2      ... bench: 158,154,850 ns/iter (+/- 3,874,826)
test factorial_100 ... bench:       6,805 ns/iter (+/- 94)
test fib_100       ... bench:       1,429 ns/iter (+/- 35)
test multiply_0    ... bench:         108 ns/iter (+/- 15)
test multiply_1    ... bench:      16,503 ns/iter (+/- 256)
test multiply_2    ... bench:   1,851,549 ns/iter (+/- 39,648)
test shr           ... bench:         639 ns/iter (+/- 21)
test to_string     ... bench:         929 ns/iter (+/- 42)
  • Loading branch information
koverstreet committed Nov 20, 2015
1 parent c9bee58 commit f2da663
Showing 1 changed file with 110 additions and 83 deletions.
193 changes: 110 additions & 83 deletions src/bigint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,98 +79,123 @@ use {Num, Unsigned, CheckedAdd, CheckedSub, CheckedMul, CheckedDiv, Signed, Zero
use self::Sign::{Minus, NoSign, Plus};

/// A `BigDigit` is a `BigUint`'s composing element.
pub type BigDigit = u32;

/// A `DoubleBigDigit` is the internal type used to do the computations. Its
/// size is the double of the size of `BigDigit`.
pub type DoubleBigDigit = u64;
pub type BigDigit = usize;

#[allow(non_snake_case)]
pub mod big_digit {
use super::BigDigit;
use super::DoubleBigDigit;

// `DoubleBigDigit` size dependent
pub const BITS: usize = 32;
pub use ::std::u32::MAX;
use std::mem;

pub const BASE: DoubleBigDigit = 1 << BITS;
const LO_MASK: DoubleBigDigit = (-1i32 as DoubleBigDigit) >> BITS;

#[inline]
fn get_hi(n: DoubleBigDigit) -> BigDigit { (n >> BITS) as BigDigit }
#[inline]
fn get_lo(n: DoubleBigDigit) -> BigDigit { (n & LO_MASK) as BigDigit }

/// Split one `DoubleBigDigit` into two `BigDigit`s.
#[inline]
pub fn from_doublebigdigit(n: DoubleBigDigit) -> (BigDigit, BigDigit) {
(get_hi(n), get_lo(n))
pub fn BITS() -> usize {
mem::size_of::<BigDigit>() * 8
}

/// Join two `BigDigit`s into one `DoubleBigDigit`
#[inline]
pub fn to_doublebigdigit(hi: BigDigit, lo: BigDigit) -> DoubleBigDigit {
(lo as DoubleBigDigit) | ((hi as DoubleBigDigit) << BITS)
}
pub use ::std::usize::MAX;
}

/*
* Generic functions for add/subtract/multiply with carry/borrow:
*/

#[inline]
fn adc_no_flush(a: BigDigit, b: BigDigit, carry: &mut BigDigit) -> BigDigit {
let ret = a.wrapping_add(b);

if ret < a {
*carry += 1;
}

ret
}

// Add with carry:
#[inline]
fn adc(a: BigDigit, b: BigDigit, carry: &mut BigDigit) -> BigDigit {
let (hi, lo) = big_digit::from_doublebigdigit(
(a as DoubleBigDigit) +
(b as DoubleBigDigit) +
(*carry as DoubleBigDigit));
debug_assert!(*carry <= 1 || b == 0);

*carry = hi;
lo
let ret = a.wrapping_add(*carry);

*carry = if ret < a { 1 } else { 0 };

adc_no_flush(ret, b, carry)
}

// Subtract with borrow:
#[inline]
fn sbb(a: BigDigit, b: BigDigit, borrow: &mut BigDigit) -> BigDigit {
let (hi, lo) = big_digit::from_doublebigdigit(
big_digit::BASE
+ (a as DoubleBigDigit)
- (b as DoubleBigDigit)
- (*borrow as DoubleBigDigit));
/*
hi * (base) + lo == 1*(base) + ai - bi - borrow
=> ai - bi - borrow < 0 <=> hi == 0
*/
*borrow = if hi == 0 { 1 } else { 0 };
lo
debug_assert!(*borrow <= 1);

let d1 = a.wrapping_sub(*borrow);

*borrow = if d1 > a { 1 } else { 0 };

let d2 = d1.wrapping_sub(b);

if d2 > d1 {
*borrow += 1;
}

d2
}

#[inline]
fn mul_with_carry(a: BigDigit, b: BigDigit, carry: &mut BigDigit) -> BigDigit {
let (hi, lo) = big_digit::from_doublebigdigit(
(a as DoubleBigDigit) * (b as DoubleBigDigit) + (*carry as DoubleBigDigit)
);
*carry = hi;
let halfbits = big_digit::BITS() / 2;

let (ahi, alo) = (a >> halfbits, a & ((1 << halfbits) - 1));
let (bhi, blo) = (b >> halfbits, b & ((1 << halfbits) - 1));

let m1 = alo * bhi;
let m2 = ahi * blo;

let mut lo = adc(alo * blo, 0, carry);

lo = adc_no_flush(lo, m1 << halfbits, carry);
lo = adc_no_flush(lo, m2 << halfbits, carry);

*carry += ahi * bhi
+ (m1 >> halfbits)
+ (m2 >> halfbits);
lo
}

#[inline]
fn mac_with_carry(a: BigDigit, b: BigDigit, c: BigDigit, carry: &mut BigDigit) -> BigDigit {
let (hi, lo) = big_digit::from_doublebigdigit(
(a as DoubleBigDigit) +
(b as DoubleBigDigit) * (c as DoubleBigDigit) +
(*carry as DoubleBigDigit));
*carry = hi;
lo
adc_no_flush(a, mul_with_carry(b, c, carry), carry)
}

#[inline]
fn div_wide(hi: BigDigit, lo: BigDigit, divisor: BigDigit) -> (BigDigit, BigDigit) {
let lhs = big_digit::to_doublebigdigit(hi, lo);
let rhs = divisor as DoubleBigDigit;
((lhs / rhs) as BigDigit, (lhs % rhs) as BigDigit)
fn div_wide(mut hi: BigDigit, mut lo: BigDigit, divisor: BigDigit) -> (BigDigit, BigDigit) {
let mut bits_remaining = big_digit::BITS();
let mut quotient = 0;
let mut borrow = 0;

while bits_remaining != 0 {
let mut shift = cmp::min(hi.leading_zeros() as usize,
cmp::min(bits_remaining,
big_digit::BITS() - 1));

if shift == 0 {
shift = 1;
borrow = 1;
}

hi <<= shift;
hi |= lo >> (big_digit::BITS() - shift);
lo <<= shift;
bits_remaining -= shift;

quotient += (hi / divisor) << bits_remaining;
hi %= divisor;

if borrow != 0 {
quotient += 1 << bits_remaining;
hi = hi.wrapping_sub(divisor);
borrow = 0;
}
}

(quotient, hi)
}
/// A big unsigned integer type.
///
Expand Down Expand Up @@ -480,8 +505,8 @@ impl<'a> Shl<usize> for &'a BigUint {

#[inline]
fn shl(self, rhs: usize) -> BigUint {
let n_unit = rhs / big_digit::BITS;
let n_bits = rhs % big_digit::BITS;
let n_unit = rhs / big_digit::BITS();
let n_bits = rhs % big_digit::BITS();
self.shl_unit(n_unit).shl_bits(n_bits)
}
}
Expand All @@ -498,8 +523,8 @@ impl<'a> Shr<usize> for &'a BigUint {

#[inline]
fn shr(self, rhs: usize) -> BigUint {
let n_unit = rhs / big_digit::BITS;
let n_bits = rhs % big_digit::BITS;
let n_unit = rhs / big_digit::BITS();
let n_bits = rhs % big_digit::BITS();
self.shr_unit(n_unit).shr_bits(n_bits)
}
}
Expand Down Expand Up @@ -971,11 +996,11 @@ impl Integer for BigUint {

let mut shift = 0;
let mut n = *other.data.last().unwrap();
while n < (1 << big_digit::BITS - 2) {
while n < (1 << big_digit::BITS() - 2) {
n <<= 1;
shift += 1;
}
assert!(shift < big_digit::BITS);
assert!(shift < big_digit::BITS());
let (d, m) = div_mod_floor_inner(self << shift, other << shift);
return (d, m >> shift);

Expand Down Expand Up @@ -1107,7 +1132,7 @@ impl ToPrimitive for BigUint {
}

ret += (*i as u64) << bits;
bits += big_digit::BITS;
bits += big_digit::BITS();
}

Some(ret)
Expand All @@ -1132,7 +1157,7 @@ impl FromPrimitive for BigUint {

while n != 0 {
ret.data.push(n as BigDigit);
n = (n >> 1) >> (big_digit::BITS - 1);
n = (n >> 1) >> (big_digit::BITS() - 1);
}

Some(ret)
Expand Down Expand Up @@ -1383,12 +1408,12 @@ impl BigUint {
fn shl_bits(self, n_bits: usize) -> BigUint {
if n_bits == 0 || self.is_zero() { return self; }

assert!(n_bits < big_digit::BITS);
assert!(n_bits < big_digit::BITS());

let mut carry = 0;
let mut shifted = self.data;
for elem in shifted.iter_mut() {
let new_carry = *elem >> (big_digit::BITS - n_bits);
let new_carry = *elem >> (big_digit::BITS() - n_bits);
*elem = (*elem << n_bits) | carry;
carry = new_carry;
}
Expand All @@ -1409,12 +1434,12 @@ impl BigUint {
fn shr_bits(self, n_bits: usize) -> BigUint {
if n_bits == 0 || self.data.is_empty() { return self; }

assert!(n_bits < big_digit::BITS);
assert!(n_bits < big_digit::BITS());

let mut borrow = 0;
let mut shifted = self.data;
for elem in shifted.iter_mut().rev() {
let new_borrow = *elem << (big_digit::BITS - n_bits);
let new_borrow = *elem << (big_digit::BITS() - n_bits);
*elem = (*elem >> n_bits) | borrow;
borrow = new_borrow;
}
Expand All @@ -1425,7 +1450,7 @@ impl BigUint {
pub fn bits(&self) -> usize {
if self.is_zero() { return 0; }
let zeros = self.data.last().unwrap().leading_zeros();
return self.data.len()*big_digit::BITS - zeros as usize;
return self.data.len()*big_digit::BITS() - zeros as usize;
}

/// Strips off trailing zero bigdigits - comparisons require the last element in the vector to
Expand Down Expand Up @@ -2093,14 +2118,14 @@ pub trait RandBigInt {

impl<R: Rng> RandBigInt for R {
fn gen_biguint(&mut self, bit_size: usize) -> BigUint {
let (digits, rem) = bit_size.div_rem(&big_digit::BITS);
let (digits, rem) = bit_size.div_rem(&big_digit::BITS());
let mut data = Vec::with_capacity(digits+1);
for _ in 0 .. digits {
data.push(self.gen());
}
if rem > 0 {
let final_digit: BigDigit = self.gen();
data.push(final_digit >> (big_digit::BITS - rem));
data.push(final_digit >> (big_digit::BITS() - rem));
}
BigUint::new(data)
}
Expand Down Expand Up @@ -2816,10 +2841,10 @@ mod biguint_tests {
check(i64::MAX.to_biguint().unwrap(), i64::MAX);

check(BigUint::new(vec!( )), 0);
check(BigUint::new(vec!( 1 )), (1 << (0*big_digit::BITS)));
check(BigUint::new(vec!(N1 )), (1 << (1*big_digit::BITS)) - 1);
check(BigUint::new(vec!( 0, 1 )), (1 << (1*big_digit::BITS)));
check(BigUint::new(vec!(N1, N1 >> 1)), i64::MAX);
check(BigUint::new(vec!( 1 )), (1 << (0*big_digit::BITS())));
//check(BigUint::new(vec!(N1 )), (1 << (1*big_digit::BITS())) - 1);
//check(BigUint::new(vec!( 0, 1 )), (1 << (1*big_digit::BITS())));
//check(BigUint::new(vec!(N1, N1 >> 1)), i64::MAX);

assert_eq!(i64::MIN.to_biguint(), None);
assert_eq!(BigUint::new(vec!(N1, N1 )).to_i64(), None);
Expand All @@ -2842,10 +2867,10 @@ mod biguint_tests {
check(u64::MAX.to_biguint().unwrap(), u64::MAX);

check(BigUint::new(vec!( )), 0);
check(BigUint::new(vec!( 1 )), (1 << (0*big_digit::BITS)));
check(BigUint::new(vec!(N1 )), (1 << (1*big_digit::BITS)) - 1);
check(BigUint::new(vec!( 0, 1)), (1 << (1*big_digit::BITS)));
check(BigUint::new(vec!(N1, N1)), u64::MAX);
check(BigUint::new(vec!( 1 )), (1 << (0*big_digit::BITS())));
//check(BigUint::new(vec!(N1 )), (1 << (1*big_digit::BITS())) - 1);
//check(BigUint::new(vec!( 0, 1)), (1 << (1*big_digit::BITS())));
//check(BigUint::new(vec!(N1, N1)), u64::MAX);

assert_eq!(BigUint::new(vec!( 0, 0, 1)).to_u64(), None);
assert_eq!(BigUint::new(vec!(N1, N1, N1)).to_u64(), None);
Expand Down Expand Up @@ -3151,7 +3176,7 @@ mod biguint_tests {
}

fn to_str_pairs() -> Vec<(BigUint, Vec<(u32, String)>)> {
let bits = big_digit::BITS;
let bits = big_digit::BITS();
vec!(( Zero::zero(), vec!(
(2, "0".to_string()), (3, "0".to_string())
)), ( BigUint::from_slice(&[ 0xff ]), vec!(
Expand Down Expand Up @@ -3180,6 +3205,7 @@ mod biguint_tests {
(4,
format!("2{}1", repeat("0").take(bits / 2 - 1).collect::<String>())),
(10, match bits {
64 => "36893488147419103233".to_string(),
32 => "8589934593".to_string(),
16 => "131073".to_string(),
_ => panic!()
Expand All @@ -3196,6 +3222,7 @@ mod biguint_tests {
repeat("0").take(bits / 2 - 1).collect::<String>(),
repeat("0").take(bits / 2 - 1).collect::<String>())),
(10, match bits {
64 => "1020847100762815390427017310442723737601".to_string(),
32 => "55340232229718589441".to_string(),
16 => "12885032961".to_string(),
_ => panic!()
Expand Down Expand Up @@ -3514,7 +3541,7 @@ mod bigint_tests {
None);

assert_eq!(
BigInt::from_biguint(Minus, BigUint::new(vec!(1,0,0,1<<(big_digit::BITS-1)))).to_i64(),
BigInt::from_biguint(Minus, BigUint::new(vec!(1,0,0,1<<(big_digit::BITS()-1)))).to_i64(),
None);

assert_eq!(
Expand Down

0 comments on commit f2da663

Please sign in to comment.