rustfs-erasure-codec 7.0.1

Rust implementation of Reed-Solomon erasure coding
Documentation
#[cfg(feature = "std")]
use core::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "std")]
use std::sync::OnceLock;

#[cfg(feature = "std")]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct RustNeonProfileStats {
    pub mul_calls: usize,
    pub mul_xor_calls: usize,
    pub total_bytes: usize,
    pub vector_64b_chunks: usize,
    pub vector_16b_chunks: usize,
    pub tail_bytes: usize,
    pub tail_calls: usize,
    pub table_lookups: usize,
}

#[cfg(feature = "std")]
impl RustNeonProfileStats {
    pub fn saturating_sub(self, baseline: Self) -> Self {
        Self {
            mul_calls: self.mul_calls.saturating_sub(baseline.mul_calls),
            mul_xor_calls: self.mul_xor_calls.saturating_sub(baseline.mul_xor_calls),
            total_bytes: self.total_bytes.saturating_sub(baseline.total_bytes),
            vector_64b_chunks: self
                .vector_64b_chunks
                .saturating_sub(baseline.vector_64b_chunks),
            vector_16b_chunks: self
                .vector_16b_chunks
                .saturating_sub(baseline.vector_16b_chunks),
            tail_bytes: self.tail_bytes.saturating_sub(baseline.tail_bytes),
            tail_calls: self.tail_calls.saturating_sub(baseline.tail_calls),
            table_lookups: self.table_lookups.saturating_sub(baseline.table_lookups),
        }
    }
}

#[cfg(feature = "std")]
#[derive(Debug, Default)]
pub(crate) struct RustNeonProfileMetrics {
    mul_calls: AtomicUsize,
    mul_xor_calls: AtomicUsize,
    total_bytes: AtomicUsize,
    vector_64b_chunks: AtomicUsize,
    vector_16b_chunks: AtomicUsize,
    tail_bytes: AtomicUsize,
    tail_calls: AtomicUsize,
    table_lookups: AtomicUsize,
}

#[cfg(feature = "std")]
impl RustNeonProfileMetrics {
    fn snapshot(&self) -> RustNeonProfileStats {
        RustNeonProfileStats {
            mul_calls: self.mul_calls.load(Ordering::Relaxed),
            mul_xor_calls: self.mul_xor_calls.load(Ordering::Relaxed),
            total_bytes: self.total_bytes.load(Ordering::Relaxed),
            vector_64b_chunks: self.vector_64b_chunks.load(Ordering::Relaxed),
            vector_16b_chunks: self.vector_16b_chunks.load(Ordering::Relaxed),
            tail_bytes: self.tail_bytes.load(Ordering::Relaxed),
            tail_calls: self.tail_calls.load(Ordering::Relaxed),
            table_lookups: self.table_lookups.load(Ordering::Relaxed),
        }
    }

    fn reset(&self) {
        self.mul_calls.store(0, Ordering::Relaxed);
        self.mul_xor_calls.store(0, Ordering::Relaxed);
        self.total_bytes.store(0, Ordering::Relaxed);
        self.vector_64b_chunks.store(0, Ordering::Relaxed);
        self.vector_16b_chunks.store(0, Ordering::Relaxed);
        self.tail_bytes.store(0, Ordering::Relaxed);
        self.tail_calls.store(0, Ordering::Relaxed);
        self.table_lookups.store(0, Ordering::Relaxed);
    }

    #[cfg(all(
        feature = "simd-neon",
        target_arch = "aarch64",
        not(target_env = "msvc"),
        not(any(target_os = "android", target_os = "ios"))
    ))]
    pub(crate) fn record_call(
        &self,
        is_xor: bool,
        input_len: usize,
        vector_64b_chunks: usize,
        vector_16b_chunks: usize,
        tail_bytes: usize,
    ) {
        if is_xor {
            self.mul_xor_calls.fetch_add(1, Ordering::Relaxed);
        } else {
            self.mul_calls.fetch_add(1, Ordering::Relaxed);
        }
        self.total_bytes.fetch_add(input_len, Ordering::Relaxed);
        self.vector_64b_chunks
            .fetch_add(vector_64b_chunks, Ordering::Relaxed);
        self.vector_16b_chunks
            .fetch_add(vector_16b_chunks, Ordering::Relaxed);
        if tail_bytes > 0 {
            self.tail_calls.fetch_add(1, Ordering::Relaxed);
            self.tail_bytes.fetch_add(tail_bytes, Ordering::Relaxed);
        }
        let lookups = vector_64b_chunks
            .saturating_mul(8)
            .saturating_add(vector_16b_chunks.saturating_mul(2));
        self.table_lookups.fetch_add(lookups, Ordering::Relaxed);
    }
}

#[cfg(feature = "std")]
pub(crate) static RUST_NEON_PROFILE_METRICS: RustNeonProfileMetrics = RustNeonProfileMetrics {
    mul_calls: AtomicUsize::new(0),
    mul_xor_calls: AtomicUsize::new(0),
    total_bytes: AtomicUsize::new(0),
    vector_64b_chunks: AtomicUsize::new(0),
    vector_16b_chunks: AtomicUsize::new(0),
    tail_bytes: AtomicUsize::new(0),
    tail_calls: AtomicUsize::new(0),
    table_lookups: AtomicUsize::new(0),
};

#[cfg(feature = "std")]
const RS_NEON_MUL_SLICE_XOR_UNROLL_ENV: &str = "RS_NEON_MUL_SLICE_XOR_UNROLL";
#[cfg(feature = "std")]
pub(crate) const RS_NEON_MUL_SLICE_XOR_SCHEDULE_ENV: &str = "RS_NEON_MUL_SLICE_XOR_SCHEDULE";

#[cfg(feature = "std")]
pub(crate) fn parse_rust_neon_xor_unroll(value: &str) -> Option<usize> {
    match value {
        "2" => Some(2),
        "4" => Some(4),
        _ => None,
    }
}

#[cfg(feature = "std")]
pub(crate) fn rust_neon_mul_slice_xor_unroll() -> usize {
    static UNROLL: OnceLock<usize> = OnceLock::new();
    *UNROLL.get_or_init(|| {
        std::env::var(RS_NEON_MUL_SLICE_XOR_UNROLL_ENV)
            .ok()
            .as_deref()
            .and_then(parse_rust_neon_xor_unroll)
            .unwrap_or(4)
    })
}

#[cfg(feature = "std")]
pub(crate) fn rust_neon_mul_slice_xor_schedule_split() -> bool {
    static SPLIT: OnceLock<bool> = OnceLock::new();
    *SPLIT.get_or_init(|| {
        std::env::var(RS_NEON_MUL_SLICE_XOR_SCHEDULE_ENV)
            .ok()
            .is_some_and(|value| value == "split")
    })
}

#[cfg(feature = "std")]
pub fn rust_neon_profile_stats() -> RustNeonProfileStats {
    RUST_NEON_PROFILE_METRICS.snapshot()
}

#[cfg(feature = "std")]
pub fn reset_rust_neon_profile_stats() {
    RUST_NEON_PROFILE_METRICS.reset();
}