use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;
use crate::{integer, string};
struct Counters {
offers: AtomicU64,
kept: AtomicU64,
nanos: AtomicU64,
}
impl Counters {
const fn zero() -> Self {
Self { offers: AtomicU64::new(0), kept: AtomicU64::new(0), nanos: AtomicU64::new(0) }
}
}
static INTEGERS: [Counters; 8] = [const { Counters::zero() }; 8];
static STRINGS: [Counters; 7] = [const { Counters::zero() }; 7];
pub const CHOOSING: &str = "(choosing)";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Codec {
pub family: &'static str,
pub name: &'static str,
pub offers: u64,
pub kept: u64,
pub nanos: u64,
}
#[must_use]
pub fn codecs() -> Vec<Codec> {
let integers = integer::Kind::ALL.iter().map(|kind| kind.name()).chain([CHOOSING]);
let strings = string::Kind::ALL.iter().map(|kind| kind.name()).chain([CHOOSING]);
let integers = integers.zip(&INTEGERS).map(|(name, counters)| ("integer", name, counters));
let strings = strings.zip(&STRINGS).map(|(name, counters)| ("string", name, counters));
integers
.chain(strings)
.map(|(family, name, counters)| Codec {
family,
name,
offers: counters.offers.load(Ordering::Relaxed),
kept: counters.kept.load(Ordering::Relaxed),
nanos: counters.nanos.load(Ordering::Relaxed),
})
.collect()
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Family {
Integer,
String,
}
impl Family {
fn slots(self) -> &'static [Counters] {
match self {
Self::Integer => &INTEGERS,
Self::String => &STRINGS,
}
}
}
fn since(started: Instant) -> u64 {
u64::try_from(started.elapsed().as_nanos()).unwrap_or(u64::MAX)
}
pub(crate) fn chose(family: Family, started: Instant) {
if let Some(counters) = family.slots().last() {
counters.nanos.fetch_add(since(started), Ordering::Relaxed);
}
}
pub(crate) fn offer<T>(family: Family, tag: u8, encode: impl FnOnce() -> T) -> T {
let started = Instant::now();
let out = encode();
if let Some(counters) = family.slots().get(usize::from(tag)) {
counters.offers.fetch_add(1, Ordering::Relaxed);
counters.nanos.fetch_add(since(started), Ordering::Relaxed);
}
out
}
pub(crate) fn kept(family: Family, tag: u8) {
if let Some(counters) = family.slots().get(usize::from(tag)) {
counters.kept.fetch_add(1, Ordering::Relaxed);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn of(family: &str, name: &str) -> Codec {
codecs().into_iter().find(|codec| codec.family == family && codec.name == name).unwrap()
}
#[test]
fn a_top_level_chunk_counts_its_offers_and_the_one_it_kept() {
let kept = |family: &str| -> u64 {
codecs().iter().filter(|codec| codec.family == family).map(|codec| codec.kept).sum()
};
let before = (of("integer", "FOR+BITPACK"), of("integer", "RLE"), of("string", "PLAIN"));
let kept_before = (kept("integer"), kept("string"));
let runs: Vec<i64> = (0..4096).map(|at| at / 512).collect();
integer::encode(&runs).unwrap();
string::encode(&[b"a".as_slice(), b"b", b"c"]).unwrap();
let after = (of("integer", "FOR+BITPACK"), of("integer", "RLE"), of("string", "PLAIN"));
assert!(after.0.offers > before.0.offers);
assert!(after.1.offers > before.1.offers);
assert!(after.2.offers > before.2.offers);
assert!(kept("integer") > kept_before.0);
assert!(kept("string") > kept_before.1);
}
#[test]
fn every_codec_of_both_families_has_a_row() {
let rows = codecs();
assert_eq!(rows.len(), integer::Kind::ALL.len() + string::Kind::ALL.len() + 2);
assert!(rows.iter().any(|codec| codec.family == "string" && codec.name == "LZ"));
}
}