dinah 0.1.0

Rust-first modular runtime and high-performance compute foundation
Documentation
use std::hint::black_box;
use std::time::{Duration, Instant};

use dinah::simulation::{Simulation, SimulationClock, SimulationEntities};

const SAMPLES: usize = 9;
const ITERATIONS_PER_SAMPLE: usize = 32;
const SIZES: &[usize] = &[64, 1_024, 16_384];
const LOOKUPS_PER_SAMPLE: usize = 16;

fn measure<F: FnMut()>(mut operation: F) -> Vec<Duration> {
    let mut samples = Vec::with_capacity(SAMPLES);
    for _ in 0..SAMPLES {
        let start = Instant::now();
        for _ in 0..ITERATIONS_PER_SAMPLE {
            operation();
        }
        samples.push(start.elapsed() / ITERATIONS_PER_SAMPLE as u32);
    }
    samples
}

fn report(case: &str, operations: usize, samples: &[Duration]) {
    let mut values: Vec<_> = samples.iter().map(Duration::as_nanos).collect();
    values.sort_unstable();
    let median = values[values.len() / 2];
    let min = values[0];
    let max = values[values.len() - 1];
    println!(
        "case={case} operations={operations} samples={SAMPLES} iterations_per_sample={ITERATIONS_PER_SAMPLE} min_op_ns={min} median_op_ns={median} max_op_ns={max} ns_per_operation={:.3}",
        median as f64 / operations.max(1) as f64,
    );
}

fn main() {
    println!("dinah Simulation benchmark; build=release; statistic=median; samples={SAMPLES}; iterations_per_sample={ITERATIONS_PER_SAMPLE}");
    println!(
        "target={}; results are machine-specific observations, not guarantees",
        std::env::consts::ARCH
    );

    let clock_samples = measure(|| {
        let mut clock = SimulationClock::new(Duration::from_millis(16)).expect("non-zero delta");
        for _ in 0..256 {
            black_box(
                clock
                    .advance()
                    .expect("tick and elapsed time do not overflow"),
            );
        }
        black_box((clock.tick(), clock.elapsed()));
    });
    report(
        "simulation_clock_256_advances_including_setup",
        256,
        &clock_samples,
    );

    let step_samples = measure(|| {
        let mut simulation =
            Simulation::new(0_u64, Duration::from_millis(16)).expect("non-zero delta");
        for _ in 0..256 {
            simulation
                .step(|state, time| {
                    *state = state.wrapping_add(time.tick);
                    Ok::<(), ()>(())
                })
                .expect("update succeeds");
        }
        black_box((simulation.state(), simulation.clock().tick()));
    });
    report(
        "simulation_step_256_updates_including_setup",
        256,
        &step_samples,
    );

    for &size in SIZES {
        let mut entities = SimulationEntities::new();
        for value in 0..size {
            entities
                .insert(value as u64)
                .expect("ID space is sufficient for benchmark size");
        }
        let update_samples = measure(|| {
            entities.for_each_mut(|id, state| {
                *state = state.wrapping_add(id.get());
            });
            black_box(entities.len());
        });
        report("simulation_entities_for_each_mut", size, &update_samples);

        let ids: Vec<_> = entities
            .iter()
            .step_by((size / LOOKUPS_PER_SAMPLE).max(1))
            .take(LOOKUPS_PER_SAMPLE)
            .map(|(id, _)| id)
            .collect();
        let lookup_samples = measure(|| {
            let checksum = ids.iter().fold(0_u64, |sum, &id| {
                sum.wrapping_add(*black_box(
                    entities.get(id).expect("sampled entity remains present"),
                ))
            });
            black_box(checksum);
        });
        report(
            "simulation_entities_16_spread_get_lookups_linear_search",
            ids.len(),
            &lookup_samples,
        );
    }
}