From 678c6fad94c5a0631b9ecd72f1f963c77096e5f9 Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Sun, 17 Sep 2023 14:53:16 +0200 Subject: [PATCH 1/5] checked_ilog: improve performance --- library/core/src/num/int_macros.rs | 8 ++++---- library/core/src/num/uint_macros.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 1f43520e1b30a..aed6a34788d86 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2477,18 +2477,18 @@ macro_rules! int_impl { None } else { let mut n = 0; - let mut r = self; + let mut r = 1; // Optimization for 128 bit wide integers. if Self::BITS == 128 { let b = Self::ilog2(self) / (Self::ilog2(base) + 1); n += b; - r /= base.pow(b as u32); + r *= base.pow(b); } - while r >= base { - r /= base; + while r <= self / base { n += 1; + r *= base; } Some(n) } diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index 23ca37817d4fd..d7f2ad8c70228 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -810,18 +810,18 @@ macro_rules! uint_impl { None } else { let mut n = 0; - let mut r = self; + let mut r = 1; // Optimization for 128 bit wide integers. if Self::BITS == 128 { let b = Self::ilog2(self) / (Self::ilog2(base) + 1); n += b; - r /= base.pow(b as u32); + r *= base.pow(b); } - while r >= base { - r /= base; + while r <= self / base { n += 1; + r *= base; } Some(n) } From 91af5f59b4301f455094443050ac640e26b3547e Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Mon, 18 Sep 2023 14:55:37 +0200 Subject: [PATCH 2/5] checked_ilog: set `n` and `r` directly avoiding arithmetic operations --- library/core/src/num/int_macros.rs | 5 ++--- library/core/src/num/uint_macros.rs | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index aed6a34788d86..968ff8d833238 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2481,9 +2481,8 @@ macro_rules! int_impl { // Optimization for 128 bit wide integers. if Self::BITS == 128 { - let b = Self::ilog2(self) / (Self::ilog2(base) + 1); - n += b; - r *= base.pow(b); + n = self.ilog2() / (base.ilog2() + 1); + r = base.pow(n); } while r <= self / base { diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index d7f2ad8c70228..ce4a0ee98b168 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -814,9 +814,8 @@ macro_rules! uint_impl { // Optimization for 128 bit wide integers. if Self::BITS == 128 { - let b = Self::ilog2(self) / (Self::ilog2(base) + 1); - n += b; - r *= base.pow(b); + n = self.ilog2() / (base.ilog2() + 1); + r = base.pow(n); } while r <= self / base { From 0c80243e149e522f28eb3650f42d82a0c5226491 Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Mon, 18 Sep 2023 14:57:32 +0200 Subject: [PATCH 3/5] checked_ilog: add comments explaining the correctness of the 128 bit optimization --- library/core/src/num/int_macros.rs | 8 ++++++++ library/core/src/num/uint_macros.rs | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 968ff8d833238..6deb4acb28222 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2481,6 +2481,14 @@ macro_rules! int_impl { // Optimization for 128 bit wide integers. if Self::BITS == 128 { + // The following is a correct lower bound for ⌊log(base,self)⌋ because + // + // log(base,self) = log(2,self) / log(2,base) + // ≥ ⌊log(2,self)⌋ / (⌊log(2,base)⌋ + 1) + // + // hence + // + // ⌊log(base,self)⌋ ≥ ⌊ ⌊log(2,self)⌋ / (⌊log(2,base)⌋ + 1) ⌋ . n = self.ilog2() / (base.ilog2() + 1); r = base.pow(n); } diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index ce4a0ee98b168..78f8bb27a5dac 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -814,6 +814,14 @@ macro_rules! uint_impl { // Optimization for 128 bit wide integers. if Self::BITS == 128 { + // The following is a correct lower bound for ⌊log(base,self)⌋ because + // + // log(base,self) = log(2,self) / log(2,base) + // ≥ ⌊log(2,self)⌋ / (⌊log(2,base)⌋ + 1) + // + // hence + // + // ⌊log(base,self)⌋ ≥ ⌊ ⌊log(2,self)⌋ / (⌊log(2,base)⌋ + 1) ⌋ . n = self.ilog2() / (base.ilog2() + 1); r = base.pow(n); } From 0f9a4d9ab45f3f2054e7909425d288ce3fa43e11 Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Mon, 18 Sep 2023 15:15:52 +0200 Subject: [PATCH 4/5] checked_ilog: add benchmarks --- library/core/benches/num/int_log/mod.rs | 79 +++++++++++++++++++++++-- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/library/core/benches/num/int_log/mod.rs b/library/core/benches/num/int_log/mod.rs index bb61224b5baad..3807cd5d76cfc 100644 --- a/library/core/benches/num/int_log/mod.rs +++ b/library/core/benches/num/int_log/mod.rs @@ -1,7 +1,7 @@ use rand::Rng; use test::{black_box, Bencher}; -macro_rules! int_log_bench { +macro_rules! int_log10_bench { ($t:ty, $predictable:ident, $random:ident, $random_small:ident) => { #[bench] fn $predictable(bench: &mut Bencher) { @@ -51,8 +51,75 @@ macro_rules! int_log_bench { }; } -int_log_bench! {u8, u8_log10_predictable, u8_log10_random, u8_log10_random_small} -int_log_bench! {u16, u16_log10_predictable, u16_log10_random, u16_log10_random_small} -int_log_bench! {u32, u32_log10_predictable, u32_log10_random, u32_log10_random_small} -int_log_bench! {u64, u64_log10_predictable, u64_log10_random, u64_log10_random_small} -int_log_bench! {u128, u128_log10_predictable, u128_log10_random, u128_log10_random_small} +int_log10_bench! {u8, u8_log10_predictable, u8_log10_random, u8_log10_random_small} +int_log10_bench! {u16, u16_log10_predictable, u16_log10_random, u16_log10_random_small} +int_log10_bench! {u32, u32_log10_predictable, u32_log10_random, u32_log10_random_small} +int_log10_bench! {u64, u64_log10_predictable, u64_log10_random, u64_log10_random_small} +int_log10_bench! {u128, u128_log10_predictable, u128_log10_random, u128_log10_random_small} + +macro_rules! int_log_bench { + ($t:ty, $random:ident, $random_small:ident, $geometric:ident) => { + #[bench] + fn $random(bench: &mut Bencher) { + let mut rng = crate::bench_rng(); + /* Exponentially distributed random numbers from the whole range of the type. */ + let numbers: Vec<$t> = (0..256) + .map(|_| { + let x = rng.gen::<$t>() >> rng.gen_range(0..<$t>::BITS); + if x >= 2 { x } else { 2 } + }) + .collect(); + bench.iter(|| { + for &b in &numbers { + for &x in &numbers { + black_box(black_box(x).ilog(b)); + } + } + }); + } + + #[bench] + fn $random_small(bench: &mut Bencher) { + let mut rng = crate::bench_rng(); + /* Exponentially distributed random numbers from the range 0..256. */ + let numbers: Vec<$t> = (0..256) + .map(|_| { + let x = (rng.gen::() >> rng.gen_range(0..u8::BITS)) as $t; + if x >= 2 { x } else { 2 } + }) + .collect(); + bench.iter(|| { + for &b in &numbers { + for &x in &numbers { + black_box(black_box(x).ilog(b)); + } + } + }); + } + + #[bench] + fn $geometric(bench: &mut Bencher) { + let bases: [$t; 16] = [2, 3, 4, 5, 7, 8, 9, 15, 16, 17, 31, 32, 33, 63, 64, 65]; + let base_and_numbers: Vec<($t, Vec<$t>)> = bases + .iter() + .map(|&b| { + let numbers = (0..=<$t>::MAX.ilog(b)).map(|exp| b.pow(exp)).collect(); + (b, numbers) + }) + .collect(); + bench.iter(|| { + for (b, numbers) in &base_and_numbers { + for &x in numbers { + black_box(black_box(x).ilog(black_box(*b))); + } + } + }); + } + }; +} + +int_log_bench! {u8, u8_log_random, u8_log_random_small, u8_log_geometric} +int_log_bench! {u16, u16_log_random, u16_log_random_small, u16_log_geometric} +int_log_bench! {u32, u32_log_random, u32_log_random_small, u32_log_geometric} +int_log_bench! {u64, u64_log_random, u64_log_random_small, u64_log_geometric} +int_log_bench! {u128, u128_log_random, u128_log_random_small, u128_log_geometric} From 3de51c95a100e4a828dd00b2fa7e9d382050449e Mon Sep 17 00:00:00 2001 From: Federico Stra Date: Fri, 22 Sep 2023 16:02:13 +0200 Subject: [PATCH 5/5] checked_ilog: remove duplication by delegating to unsigned integers --- library/core/src/num/int_macros.rs | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 6deb4acb28222..e353552cb6317 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -2476,28 +2476,9 @@ macro_rules! int_impl { if self <= 0 || base <= 1 { None } else { - let mut n = 0; - let mut r = 1; - - // Optimization for 128 bit wide integers. - if Self::BITS == 128 { - // The following is a correct lower bound for ⌊log(base,self)⌋ because - // - // log(base,self) = log(2,self) / log(2,base) - // ≥ ⌊log(2,self)⌋ / (⌊log(2,base)⌋ + 1) - // - // hence - // - // ⌊log(base,self)⌋ ≥ ⌊ ⌊log(2,self)⌋ / (⌊log(2,base)⌋ + 1) ⌋ . - n = self.ilog2() / (base.ilog2() + 1); - r = base.pow(n); - } - - while r <= self / base { - n += 1; - r *= base; - } - Some(n) + // Delegate to the unsigned implementation. + // The condition makes sure that both casts are exact. + (self as $UnsignedT).checked_ilog(base as $UnsignedT) } }