use std::sync::atomic::{AtomicU32, Ordering};
pub struct ArrowCompressionRatioEstimator {
ratio_bits: AtomicU32,
}
const COMPRESSION_RATIO_IMPROVING_STEP: f32 = 0.005;
const COMPRESSION_RATIO_DETERIORATE_STEP: f32 = 0.05;
const DEFAULT_COMPRESSION_RATIO: f32 = 1.0;
impl ArrowCompressionRatioEstimator {
pub fn new() -> Self {
Self {
ratio_bits: AtomicU32::new(DEFAULT_COMPRESSION_RATIO.to_bits()),
}
}
pub fn estimation(&self) -> f32 {
f32::from_bits(self.ratio_bits.load(Ordering::Relaxed))
}
pub fn update_estimation(&self, observed_ratio: f32) {
let current = self.estimation();
let new_ratio = if observed_ratio > current {
(current + COMPRESSION_RATIO_DETERIORATE_STEP).max(observed_ratio)
} else if observed_ratio < current {
(current - COMPRESSION_RATIO_IMPROVING_STEP).max(observed_ratio)
} else {
return;
};
self.ratio_bits
.store(new_ratio.to_bits(), Ordering::Relaxed);
}
}
impl Default for ArrowCompressionRatioEstimator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_ratio_is_one() {
let e = ArrowCompressionRatioEstimator::new();
assert_eq!(e.estimation(), 1.0);
}
#[test]
fn test_deterioration_jumps_quickly() {
let e = ArrowCompressionRatioEstimator::new();
e.update_estimation(1.1);
assert!(e.estimation() >= 1.05);
}
#[test]
fn test_improvement_moves_slowly() {
let e = ArrowCompressionRatioEstimator::new();
e.update_estimation(0.5);
assert!((e.estimation() - 0.995).abs() < 0.001);
}
#[test]
fn test_converges_to_observed() {
let e = ArrowCompressionRatioEstimator::new();
for _ in 0..1000 {
e.update_estimation(0.7);
}
assert!((e.estimation() - 0.7).abs() < 0.01);
}
}