downsample 0.0.5

keep downsampled history of data over long period of time
Documentation
use core::{hint::black_box, num::NonZeroUsize};

use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use downsample::{FixedFrequencyBuilder, Reducer, reducers};

fn nz(value: usize) -> NonZeroUsize { NonZeroUsize::new(value).unwrap() }

fn day_history_average() -> downsample::FixedWriter<f32> {
    FixedFrequencyBuilder::new(nz(100))
        .level(nz(59), nz(100), reducers::average_f32())
        .level(nz(59), nz(60), reducers::average_f32())
        .level(nz(23), nz(60), reducers::average_f32())
        .build()
}

fn dense_rollover_average() -> downsample::FixedWriter<f32> {
    FixedFrequencyBuilder::new(nz(16))
        .level(nz(16), nz(4), reducers::average_f32())
        .level(nz(16), nz(4), reducers::average_f32())
        .level(nz(16), nz(4), reducers::average_f32())
        .level(nz(16), nz(4), reducers::average_f32())
        .build()
}

fn median_rollover() -> downsample::FixedWriter<f32> {
    FixedFrequencyBuilder::new(nz(64))
        .level(nz(32), nz(64), reducers::median_f32())
        .level(nz(32), nz(16), reducers::median_f32())
        .build()
}

fn fill(writer: &mut downsample::FixedWriter<f32>, count: usize) {
    for value in 0..count {
        writer.push((value % 10_000) as f32 * 0.25);
    }
}

fn builder(c: &mut Criterion) {
    c.bench_function("builder/day_history_average", |b| {
        b.iter(|| black_box(day_history_average()))
    });
}

fn push_single(c: &mut Criterion) {
    let mut writer = day_history_average();
    c.bench_function("push/single_steady_state_average", |b| {
        b.iter(|| writer.push(black_box(123.456)));
    });
}

fn push_batches(c: &mut Criterion) {
    let mut group = c.benchmark_group("push/batch");

    for count in [1_000, 100_000] {
        group.throughput(Throughput::Elements(count as u64));
        group.bench_with_input(BenchmarkId::new("day_history_average", count), &count, |b, &count| {
            b.iter_batched(
                day_history_average,
                |mut writer| {
                    for value in 0..count {
                        writer.push(black_box(value as f32));
                    }
                    black_box(writer)
                },
                BatchSize::SmallInput,
            );
        });
    }

    group.throughput(Throughput::Elements(100_000));
    group.bench_function("dense_rollover_average/100000", |b| {
        b.iter_batched(
            dense_rollover_average,
            |mut writer| {
                for value in 0..100_000 {
                    writer.push(black_box(value as f32));
                }
                black_box(writer)
            },
            BatchSize::SmallInput,
        );
    });

    group.throughput(Throughput::Elements(10_000));
    group.bench_function("median_rollover/10000", |b| {
        b.iter_batched(
            median_rollover,
            |mut writer| {
                for value in 0..10_000 {
                    writer.push(black_box((value % 251) as f32));
                }
                black_box(writer)
            },
            BatchSize::SmallInput,
        );
    });

    group.finish();
}

fn read(c: &mut Criterion) {
    let mut writer = day_history_average();
    fill(&mut writer, 100_000);

    let mut group = c.benchmark_group("read");

    group.throughput(Throughput::Elements(1_000_000));
    group.bench_function("indexed_level_lookup", |b| {
        b.iter(|| {
            for index in 0..1_000_000 {
                black_box(writer.storage().get_back_by_level(1, index % 59));
            }
        });
    });

    let view = writer.storage().level(1).unwrap();
    group.throughput(Throughput::Elements(view.len() as u64));
    group.bench_function("iterate_level_oldest_first", |b| {
        b.iter(|| {
            let mut total = 0.0;
            for value in view.iter_oldest_first() {
                total += *black_box(value);
            }
            black_box(total)
        });
    });

    group.finish();
}

fn reduce_many<T>(reducer: Reducer<T>, first: &[T], second: &[T], iterations: usize) -> T
where
    T: Copy + Default,
{
    let mut value = T::default();
    for _ in 0..iterations {
        value = reducer.reduce(black_box(first), black_box(second));
        black_box(value);
    }
    value
}

fn reducers(c: &mut Criterion) {
    let contiguous_f32: Vec<f32> = (0..1024).map(|value| value as f32 * 0.25).collect();
    let split_f32_a: Vec<f32> = (0..257).map(|value| value as f32).collect();
    let split_f32_b: Vec<f32> = (257..1024).map(|value| value as f32).collect();
    let median_u32: Vec<u32> = (0..1024).rev().collect();

    let mut group = c.benchmark_group("reducers");

    group.throughput(Throughput::Elements(1024));
    group.bench_function("average_f32/contiguous_1024", |b| {
        b.iter(|| {
            black_box(reducers::average_f32().reduce(black_box(&contiguous_f32), black_box(&[])));
        });
    });
    group.bench_function("average_f32/split_1024", |b| {
        b.iter(|| {
            black_box(reducers::average_f32().reduce(black_box(&split_f32_a), black_box(&split_f32_b)));
        });
    });
    group.bench_function("min_f32/split_1024", |b| {
        b.iter(|| {
            black_box(reducers::min::<f32>().reduce(black_box(&split_f32_a), black_box(&split_f32_b)));
        });
    });
    group.bench_function("median_u32/contiguous_1024", |b| {
        b.iter(|| {
            black_box(reducers::median::<u32>().reduce(black_box(&median_u32), black_box(&[])));
        });
    });
    group.bench_function("median_f32/split_1024", |b| {
        b.iter(|| {
            black_box(reducers::median_f32().reduce(black_box(&split_f32_a), black_box(&split_f32_b)));
        });
    });

    group.throughput(Throughput::Elements(1024 * 1_000));
    group.bench_function("average_f32/split_1024_x1000", |b| {
        b.iter(|| black_box(reduce_many(reducers::average_f32(), &split_f32_a, &split_f32_b, 1_000)));
    });

    group.finish();
}

criterion_group!(benches, builder, push_single, push_batches, read, reducers);
criterion_main!(benches);