415 lines
13 KiB
Rust
415 lines
13 KiB
Rust
//! Benchmark sqrt and cbrt
|
|
|
|
#![feature(test)]
|
|
|
|
extern crate num_integer;
|
|
extern crate num_traits;
|
|
extern crate test;
|
|
|
|
use num_integer::Integer;
|
|
use num_traits::{AsPrimitive, PrimInt, WrappingAdd, WrappingMul};
|
|
use std::cmp::{max, min};
|
|
use std::fmt::Debug;
|
|
use test::{black_box, Bencher};
|
|
|
|
// --- Utilities for RNG ----------------------------------------------------
|
|
|
|
trait BenchInteger: Integer + PrimInt + WrappingAdd + WrappingMul + 'static {}
|
|
|
|
impl<T> BenchInteger for T where T: Integer + PrimInt + WrappingAdd + WrappingMul + 'static {}
|
|
|
|
// Simple PRNG so we don't have to worry about rand compatibility
|
|
fn lcg<T>(x: T) -> T
|
|
where
|
|
u32: AsPrimitive<T>,
|
|
T: BenchInteger,
|
|
{
|
|
// LCG parameters from Numerical Recipes
|
|
// (but we're applying it to arbitrary sizes)
|
|
const LCG_A: u32 = 1664525;
|
|
const LCG_C: u32 = 1013904223;
|
|
x.wrapping_mul(&LCG_A.as_()).wrapping_add(&LCG_C.as_())
|
|
}
|
|
|
|
// --- Alt. Implementations -------------------------------------------------
|
|
|
|
trait NaiveAverage {
|
|
fn naive_average_ceil(&self, other: &Self) -> Self;
|
|
fn naive_average_floor(&self, other: &Self) -> Self;
|
|
}
|
|
|
|
trait UncheckedAverage {
|
|
fn unchecked_average_ceil(&self, other: &Self) -> Self;
|
|
fn unchecked_average_floor(&self, other: &Self) -> Self;
|
|
}
|
|
|
|
trait ModuloAverage {
|
|
fn modulo_average_ceil(&self, other: &Self) -> Self;
|
|
fn modulo_average_floor(&self, other: &Self) -> Self;
|
|
}
|
|
|
|
macro_rules! naive_average {
|
|
($T:ident) => {
|
|
impl super::NaiveAverage for $T {
|
|
fn naive_average_floor(&self, other: &$T) -> $T {
|
|
match self.checked_add(*other) {
|
|
Some(z) => Integer::div_floor(&z, &2),
|
|
None => {
|
|
if self > other {
|
|
let diff = self - other;
|
|
other + Integer::div_floor(&diff, &2)
|
|
} else {
|
|
let diff = other - self;
|
|
self + Integer::div_floor(&diff, &2)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fn naive_average_ceil(&self, other: &$T) -> $T {
|
|
match self.checked_add(*other) {
|
|
Some(z) => Integer::div_ceil(&z, &2),
|
|
None => {
|
|
if self > other {
|
|
let diff = self - other;
|
|
self - Integer::div_floor(&diff, &2)
|
|
} else {
|
|
let diff = other - self;
|
|
other - Integer::div_floor(&diff, &2)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! unchecked_average {
|
|
($T:ident) => {
|
|
impl super::UncheckedAverage for $T {
|
|
fn unchecked_average_floor(&self, other: &$T) -> $T {
|
|
self.wrapping_add(*other) / 2
|
|
}
|
|
fn unchecked_average_ceil(&self, other: &$T) -> $T {
|
|
(self.wrapping_add(*other) / 2).wrapping_add(1)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
macro_rules! modulo_average {
|
|
($T:ident) => {
|
|
impl super::ModuloAverage for $T {
|
|
fn modulo_average_ceil(&self, other: &$T) -> $T {
|
|
let (q1, r1) = self.div_mod_floor(&2);
|
|
let (q2, r2) = other.div_mod_floor(&2);
|
|
q1 + q2 + (r1 | r2)
|
|
}
|
|
fn modulo_average_floor(&self, other: &$T) -> $T {
|
|
let (q1, r1) = self.div_mod_floor(&2);
|
|
let (q2, r2) = other.div_mod_floor(&2);
|
|
q1 + q2 + (r1 * r2)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// --- Bench functions ------------------------------------------------------
|
|
|
|
fn bench_unchecked<T, F>(b: &mut Bencher, v: &[(T, T)], f: F)
|
|
where
|
|
T: Integer + Debug + Copy,
|
|
F: Fn(&T, &T) -> T,
|
|
{
|
|
b.iter(|| {
|
|
for (x, y) in v {
|
|
black_box(f(x, y));
|
|
}
|
|
});
|
|
}
|
|
|
|
fn bench_ceil<T, F>(b: &mut Bencher, v: &[(T, T)], f: F)
|
|
where
|
|
T: Integer + Debug + Copy,
|
|
F: Fn(&T, &T) -> T,
|
|
{
|
|
for &(i, j) in v {
|
|
let rt = f(&i, &j);
|
|
let (a, b) = (min(i, j), max(i, j));
|
|
// if both number are the same sign, check rt is in the middle
|
|
if (a < T::zero()) == (b < T::zero()) {
|
|
if (b - a).is_even() {
|
|
assert_eq!(rt - a, b - rt);
|
|
} else {
|
|
assert_eq!(rt - a, b - rt + T::one());
|
|
}
|
|
// if both number have a different sign,
|
|
} else {
|
|
if (a + b).is_even() {
|
|
assert_eq!(rt, (a + b) / (T::one() + T::one()))
|
|
} else {
|
|
assert_eq!(rt, (a + b + T::one()) / (T::one() + T::one()))
|
|
}
|
|
}
|
|
}
|
|
bench_unchecked(b, v, f);
|
|
}
|
|
|
|
fn bench_floor<T, F>(b: &mut Bencher, v: &[(T, T)], f: F)
|
|
where
|
|
T: Integer + Debug + Copy,
|
|
F: Fn(&T, &T) -> T,
|
|
{
|
|
for &(i, j) in v {
|
|
let rt = f(&i, &j);
|
|
let (a, b) = (min(i, j), max(i, j));
|
|
// if both number are the same sign, check rt is in the middle
|
|
if (a < T::zero()) == (b < T::zero()) {
|
|
if (b - a).is_even() {
|
|
assert_eq!(rt - a, b - rt);
|
|
} else {
|
|
assert_eq!(rt - a + T::one(), b - rt);
|
|
}
|
|
// if both number have a different sign,
|
|
} else {
|
|
if (a + b).is_even() {
|
|
assert_eq!(rt, (a + b) / (T::one() + T::one()))
|
|
} else {
|
|
assert_eq!(rt, (a + b - T::one()) / (T::one() + T::one()))
|
|
}
|
|
}
|
|
}
|
|
bench_unchecked(b, v, f);
|
|
}
|
|
|
|
// --- Bench implementation -------------------------------------------------
|
|
|
|
macro_rules! bench_average {
|
|
($($T:ident),*) => {$(
|
|
mod $T {
|
|
use test::Bencher;
|
|
use num_integer::{Average, Integer};
|
|
use super::{UncheckedAverage, NaiveAverage, ModuloAverage};
|
|
use super::{bench_ceil, bench_floor, bench_unchecked};
|
|
|
|
naive_average!($T);
|
|
unchecked_average!($T);
|
|
modulo_average!($T);
|
|
|
|
const SIZE: $T = 30;
|
|
|
|
fn overflowing() -> Vec<($T, $T)> {
|
|
(($T::max_value()-SIZE)..$T::max_value())
|
|
.flat_map(|x| -> Vec<_> {
|
|
(($T::max_value()-100)..($T::max_value()-100+SIZE))
|
|
.map(|y| (x, y))
|
|
.collect()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn small() -> Vec<($T, $T)> {
|
|
(0..SIZE)
|
|
.flat_map(|x| -> Vec<_> {(0..SIZE).map(|y| (x, y)).collect()})
|
|
.collect()
|
|
}
|
|
|
|
fn rand() -> Vec<($T, $T)> {
|
|
small()
|
|
.into_iter()
|
|
.map(|(x, y)| (super::lcg(x), super::lcg(y)))
|
|
.collect()
|
|
}
|
|
|
|
mod ceil {
|
|
|
|
use super::*;
|
|
|
|
mod small {
|
|
|
|
use super::*;
|
|
|
|
#[bench]
|
|
fn optimized(b: &mut Bencher) {
|
|
let v = small();
|
|
bench_ceil(b, &v, |x: &$T, y: &$T| x.average_ceil(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn naive(b: &mut Bencher) {
|
|
let v = small();
|
|
bench_ceil(b, &v, |x: &$T, y: &$T| x.naive_average_ceil(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn unchecked(b: &mut Bencher) {
|
|
let v = small();
|
|
bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_ceil(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn modulo(b: &mut Bencher) {
|
|
let v = small();
|
|
bench_ceil(b, &v, |x: &$T, y: &$T| x.modulo_average_ceil(y));
|
|
}
|
|
}
|
|
|
|
mod overflowing {
|
|
|
|
use super::*;
|
|
|
|
#[bench]
|
|
fn optimized(b: &mut Bencher) {
|
|
let v = overflowing();
|
|
bench_ceil(b, &v, |x: &$T, y: &$T| x.average_ceil(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn naive(b: &mut Bencher) {
|
|
let v = overflowing();
|
|
bench_ceil(b, &v, |x: &$T, y: &$T| x.naive_average_ceil(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn unchecked(b: &mut Bencher) {
|
|
let v = overflowing();
|
|
bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_ceil(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn modulo(b: &mut Bencher) {
|
|
let v = overflowing();
|
|
bench_ceil(b, &v, |x: &$T, y: &$T| x.modulo_average_ceil(y));
|
|
}
|
|
}
|
|
|
|
mod rand {
|
|
|
|
use super::*;
|
|
|
|
#[bench]
|
|
fn optimized(b: &mut Bencher) {
|
|
let v = rand();
|
|
bench_ceil(b, &v, |x: &$T, y: &$T| x.average_ceil(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn naive(b: &mut Bencher) {
|
|
let v = rand();
|
|
bench_ceil(b, &v, |x: &$T, y: &$T| x.naive_average_ceil(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn unchecked(b: &mut Bencher) {
|
|
let v = rand();
|
|
bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_ceil(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn modulo(b: &mut Bencher) {
|
|
let v = rand();
|
|
bench_ceil(b, &v, |x: &$T, y: &$T| x.modulo_average_ceil(y));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
mod floor {
|
|
|
|
use super::*;
|
|
|
|
mod small {
|
|
|
|
use super::*;
|
|
|
|
#[bench]
|
|
fn optimized(b: &mut Bencher) {
|
|
let v = small();
|
|
bench_floor(b, &v, |x: &$T, y: &$T| x.average_floor(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn naive(b: &mut Bencher) {
|
|
let v = small();
|
|
bench_floor(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn unchecked(b: &mut Bencher) {
|
|
let v = small();
|
|
bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn modulo(b: &mut Bencher) {
|
|
let v = small();
|
|
bench_floor(b, &v, |x: &$T, y: &$T| x.modulo_average_floor(y));
|
|
}
|
|
}
|
|
|
|
mod overflowing {
|
|
|
|
use super::*;
|
|
|
|
#[bench]
|
|
fn optimized(b: &mut Bencher) {
|
|
let v = overflowing();
|
|
bench_floor(b, &v, |x: &$T, y: &$T| x.average_floor(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn naive(b: &mut Bencher) {
|
|
let v = overflowing();
|
|
bench_floor(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn unchecked(b: &mut Bencher) {
|
|
let v = overflowing();
|
|
bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn modulo(b: &mut Bencher) {
|
|
let v = overflowing();
|
|
bench_floor(b, &v, |x: &$T, y: &$T| x.modulo_average_floor(y));
|
|
}
|
|
}
|
|
|
|
mod rand {
|
|
|
|
use super::*;
|
|
|
|
#[bench]
|
|
fn optimized(b: &mut Bencher) {
|
|
let v = rand();
|
|
bench_floor(b, &v, |x: &$T, y: &$T| x.average_floor(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn naive(b: &mut Bencher) {
|
|
let v = rand();
|
|
bench_floor(b, &v, |x: &$T, y: &$T| x.naive_average_floor(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn unchecked(b: &mut Bencher) {
|
|
let v = rand();
|
|
bench_unchecked(b, &v, |x: &$T, y: &$T| x.unchecked_average_floor(y));
|
|
}
|
|
|
|
#[bench]
|
|
fn modulo(b: &mut Bencher) {
|
|
let v = rand();
|
|
bench_floor(b, &v, |x: &$T, y: &$T| x.modulo_average_floor(y));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
)*}
|
|
}
|
|
|
|
bench_average!(i8, i16, i32, i64, i128, isize);
|
|
bench_average!(u8, u16, u32, u64, u128, usize);
|