Skip to content

Commit

Permalink
Add in optimizations for 2^N radix integer writing.
Browse files Browse the repository at this point in the history
This adds in performance enhancements by using correct optimizations for calculating digit counts.
  • Loading branch information
Alexhuszagh committed Dec 6, 2024
1 parent 8f21471 commit 56ccd40
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 16 deletions.
48 changes: 34 additions & 14 deletions lexical-write-integer/src/digit_count.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,34 +122,46 @@ fn digit_log2<T: UnsignedInteger>(x: T) -> usize {

/// Highly-optimized digit count for base4 values.
///
/// This is very similar to base 2, except we shift right by
/// 1 and adjust by 1. For example, `fast_log2(3) == 1`, so
/// `fast_log2(3) >> 1 == 0`, which then gives us our result.
/// This is very similar to base 2, except we divide by 2
/// and adjust by 1. For example, `fast_log2(3) == 1`, so
/// `fast_log2(3) / 2 == 0`, which then gives us our result.
///
/// This works because `log2(x) / 2 == log4(x)`. Flooring is
/// the correct approach since `log2(15) == 3`, which should be
/// 2 digits (so `3 / 2 + 1`).
#[inline(always)]
fn digit_log4<T: UnsignedInteger>(x: T) -> usize {
// NOTE: This cannot be `fast_log2(fast_log2())`
(fast_log2(x | T::ONE) >> 1) + 1
(fast_log2(x) / 2) + 1
}

/// Specialized digit count for base8 values.
/// Highly-optimized digit count for base8 values.
///
/// This works because `log2(x) / 3 == log8(x)`. Flooring is
/// the correct approach since `log2(63) == 5`, which should be
/// 2 digits (so `5 / 3 + 1`).
#[inline(always)]
fn digit_log8<T: UnsignedInteger>(x: T) -> usize {
// FIXME: Optimize
digit_count!(@naive T, 8, x)
(fast_log2(x) / 3) + 1
}

/// Specialized digit count for base16 values.
/// Highly-optimized digit count for base16 values.
///
/// This works because `log2(x) / 4 == log16(x)`. Flooring is
/// the correct approach since `log2(255) == 7`, which should be
/// 2 digits (so `7 / 4 + 1`).
#[inline(always)]
fn digit_log16<T: UnsignedInteger>(x: T) -> usize {
// FIXME: Optimize
digit_count!(@naive T, 16, x)
(fast_log2(x) / 4) + 1
}

/// Specialized digit count for base32 values.
/// Highly-optimized digit count for base32 values.
///
/// This works because `log2(x) / 5 == log32(x)`. Flooring is
/// the correct approach since `log2(1023) == 9`, which should be
/// 2 digits (so `9 / 5 + 1`).
#[inline(always)]
fn digit_log32<T: UnsignedInteger>(x: T) -> usize {
// FIXME: Optimize
digit_count!(@naive T, 32, x)
(fast_log2(x) / 5) + 1
}

/// Quickly calculate the number of digits in a type.
Expand Down Expand Up @@ -183,6 +195,14 @@ pub unsafe trait DigitCount: UnsignedInteger + DecimalCount {
_ => digit_count!(@naive Self, radix, self),
}
}

/// Get the number of digits in a value, always using the slow algorithm.
///
/// This is exposed for testing purposes.
#[inline(always)]
fn slow_digit_count(self, radix: u32) -> usize {
digit_count!(@naive Self, radix, self)
}
}

// Implement digit counts for all types.
Expand Down
29 changes: 27 additions & 2 deletions lexical-write-integer/tests/digit_count_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ mod util;

use lexical_write_integer::decimal::DecimalCount;
use lexical_write_integer::digit_count::{self, DigitCount};
#[rustversion::since(1.67)]
use proptest::prelude::*;

#[rustversion::since(1.67)]
use crate::util::default_proptest_config;

#[test]
Expand Down Expand Up @@ -177,6 +175,33 @@ default_quickcheck! {
}
}

proptest! {
#![proptest_config(default_proptest_config())]

#[test]
fn decimal_slow_u64_test(x: u64) {
prop_assert_eq!(x.digit_count(10), x.slow_digit_count(10));
}

#[test]
fn basen_slow_u64_test(x: u64, power in 1u32..=5) {
let radix = 2u32.pow(power);
prop_assert_eq!(x.digit_count(radix), x.slow_digit_count(radix));
}

#[test]
fn decimal_slow_u128_test(x: u128) {
prop_assert_eq!(x.digit_count(10), x.slow_digit_count(10));
}

#[test]
#[cfg(feature = "power-of-two")]
fn basen_slow_u128_test(x: u128, power in 1u32..=5) {
let radix = 2u32.pow(power);
prop_assert_eq!(x.digit_count(radix), x.slow_digit_count(radix));
}
}

#[rustversion::since(1.67)]
macro_rules! ilog {
($x:ident, $radix:expr) => {{
Expand Down

0 comments on commit 56ccd40

Please sign in to comment.