Skip to content

Commit 08ad135

Browse files
bors[bot]lemmihcuviper
authored
Merge #98
98: Avoid two clones in 'reduce'. r=cuviper a=lemmih Hi, `reduce` needlessly clones the numerator and denominator. The "proper" way to fix this would be to add a new trait bound but that is not backwards compatible. This PR proposes a temporary fix: Take the values and leave behind zeroes. This avoids cloning and `T::zero()` usually doesn't involve heap allocations (at least they don't for `BigInt`). All in all, this PR improves performance by about 10% (1150ns -> 1050ns) when creating values from small `BigInt`s (tested on an M1). Creating a `Ratio<BigInt>` is still about 10x slower than `Ratio<isize>` but that is entirely due to problems in `num-bigint`. Note: I copied the `rng` module from `num-bigint`. Note: Adding a `NumAssignRef` bound would get rid of `g.clone()`. Co-authored-by: David Himmelstrup <lemmih@gmail.com> Co-authored-by: Josh Stone <cuviper@gmail.com>
2 parents 06d9f21 + 8506da1 commit 08ad135

File tree

5 files changed

+109
-7
lines changed

5 files changed

+109
-7
lines changed

ci/benchmarks/Cargo.toml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "benchmarks"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
10+
[dependencies.num-rational]
11+
path = "../.."
12+
default-features = false
13+
features = ["num-bigint"]
14+
15+
[dependencies.num-bigint]
16+
version = "0.4.0"
17+
default-features = false
18+
19+
[dependencies.rand]
20+
version = "0.8"
21+
default-features = false

ci/benchmarks/src/main.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#![feature(test)]
2+
3+
extern crate test;
4+
5+
use num_bigint::BigInt;
6+
use num_rational::{BigRational, Ratio};
7+
use test::Bencher;
8+
9+
mod rng;
10+
use rng::get_rng;
11+
12+
#[bench]
13+
fn alloc_ratio_bigint_bench(b: &mut Bencher) {
14+
use rand::RngCore;
15+
let mut rng = get_rng();
16+
b.iter(|| {
17+
let a = BigInt::from(rng.next_u64());
18+
let b = BigInt::from(rng.next_u64());
19+
BigRational::new(a, b)
20+
});
21+
}
22+
23+
#[bench]
24+
fn alloc_ratio_u64_bench(b: &mut Bencher) {
25+
use rand::RngCore;
26+
let mut rng = get_rng();
27+
b.iter(|| {
28+
let a = rng.next_u64();
29+
let b = rng.next_u64();
30+
Ratio::new(a, b)
31+
});
32+
}

ci/benchmarks/src/rng.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use rand::RngCore;
2+
3+
pub(crate) fn get_rng() -> impl RngCore {
4+
XorShiftStar {
5+
a: 0x0123_4567_89AB_CDEF,
6+
}
7+
}
8+
9+
/// Simple `Rng` for benchmarking without additional dependencies
10+
struct XorShiftStar {
11+
a: u64,
12+
}
13+
14+
impl RngCore for XorShiftStar {
15+
fn next_u32(&mut self) -> u32 {
16+
self.next_u64() as u32
17+
}
18+
19+
fn next_u64(&mut self) -> u64 {
20+
// https://en.wikipedia.org/wiki/Xorshift#xorshift*
21+
self.a ^= self.a >> 12;
22+
self.a ^= self.a << 25;
23+
self.a ^= self.a >> 27;
24+
self.a.wrapping_mul(0x2545_F491_4F6C_DD1D)
25+
}
26+
27+
fn fill_bytes(&mut self, dest: &mut [u8]) {
28+
for chunk in dest.chunks_mut(8) {
29+
let bytes = self.next_u64().to_le_bytes();
30+
let slice = &bytes[..chunk.len()];
31+
chunk.copy_from_slice(slice)
32+
}
33+
}
34+
35+
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
36+
Ok(self.fill_bytes(dest))
37+
}
38+
}

ci/test_full.sh

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,8 @@ done
6363
# test all supported features without std
6464
cargo build --no-default-features --features="${NO_STD_FEATURES[*]}"
6565
cargo test --no-default-features --features="${NO_STD_FEATURES[*]}"
66+
67+
# make sure benchmarks can be built and sanity-tested
68+
if rustc --version | grep -q nightly; then
69+
cargo test --manifest-path ci/benchmarks/Cargo.toml
70+
fi

src/lib.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,18 +142,24 @@ impl<T: Clone + Integer> Ratio<T> {
142142
let g: T = self.numer.gcd(&self.denom);
143143

144144
// FIXME(#5992): assignment operator overloads
145-
// self.numer /= g;
146145
// T: Clone + Integer != T: Clone + NumAssign
147-
self.numer = self.numer.clone() / g.clone();
148-
// FIXME(#5992): assignment operator overloads
146+
147+
#[inline]
148+
fn replace_with<T: Zero>(x: &mut T, f: impl FnOnce(T) -> T) {
149+
let y = core::mem::replace(x, T::zero());
150+
*x = f(y);
151+
}
152+
153+
// self.numer /= g;
154+
replace_with(&mut self.numer, |x| x / g.clone());
155+
149156
// self.denom /= g;
150-
// T: Clone + Integer != T: Clone + NumAssign
151-
self.denom = self.denom.clone() / g;
157+
replace_with(&mut self.denom, |x| x / g);
152158

153159
// keep denom positive!
154160
if self.denom < T::zero() {
155-
self.numer = T::zero() - self.numer.clone();
156-
self.denom = T::zero() - self.denom.clone();
161+
replace_with(&mut self.numer, |x| T::zero() - x);
162+
replace_with(&mut self.denom, |x| T::zero() - x);
157163
}
158164
}
159165

0 commit comments

Comments
 (0)