f8 0.2.0

A no_std, one-byte UNORM with exact rounding, saturating arithmetic, and SIMD conversion
Documentation
//! Dependency-free throughput benchmark. Run with `cargo bench --bench convert`.

use f8::f8;
use std::{hint::black_box, time::Instant};

fn measure(name: &str, len: usize, mut run: impl FnMut()) {
    for _ in 0..100 {
        run();
    }
    let iterations = (8_000_000 / len).max(16);
    let mut samples = [0.0; 7];
    for sample in &mut samples {
        let start = Instant::now();
        for _ in 0..iterations {
            run();
        }
        *sample = start.elapsed().as_secs_f64() * 1e9 / (iterations * len) as f64;
    }
    samples.sort_by(f64::total_cmp);
    println!("{name:24} {len:8} values: {:8.3} ns/value", samples[3]);
}

fn main() {
    println!("Median of seven samples; black-boxed input and output; lower is better.");
    println!("simd feature: {}", cfg!(feature = "simd"));
    for len in [16, 256, 4096, 1_048_576] {
        let mut seed = 0x1234_5678u32;
        let input: Vec<f32> = (0..len)
            .map(|_| {
                seed ^= seed << 13;
                seed ^= seed >> 17;
                seed ^= seed << 5;
                (seed >> 8) as f32 / 16_777_215.0
            })
            .collect();
        let mut packed = vec![f8::ZERO; len];
        let mut decoded = vec![0.0f32; len];

        measure("encode portable loop", len, || {
            for (&x, out) in black_box(&input).iter().zip(&mut packed) {
                *out = f8::from_f32(x);
            }
            black_box(&packed);
        });
        measure("encode bulk dispatch", len, || {
            f8::from_f32_slice(black_box(&input), &mut packed);
            black_box(&packed);
        });
        measure("encode f64 reference", len, || {
            for (&x, out) in black_box(&input).iter().zip(&mut packed) {
                *out = f8::from_bits((f64::from(x) * 255.0).round_ties_even() as u8);
            }
            black_box(&packed);
        });
        measure("decode integer loop", len, || {
            for (&x, out) in black_box(&packed).iter().zip(&mut decoded) {
                *out = x.to_f32();
            }
            black_box(&decoded);
        });
        measure("decode bulk", len, || {
            f8::to_f32_slice(black_box(&packed), &mut decoded);
            black_box(&decoded);
        });
        measure("decode division loop", len, || {
            for (&x, out) in black_box(&packed).iter().zip(&mut decoded) {
                *out = f32::from(x.to_bits()) / 255.0;
            }
            black_box(&decoded);
        });

        let rhs: Vec<_> = packed.iter().rev().copied().collect();
        let mut product = vec![f8::ZERO; len];
        measure("multiply integer loop", len, || {
            for ((&a, &b), out) in black_box(&packed)
                .iter()
                .zip(black_box(&rhs))
                .zip(&mut product)
            {
                *out = a * b;
            }
            black_box(&product);
        });
        measure("multiply f64 reference", len, || {
            for ((&a, &b), out) in black_box(&packed)
                .iter()
                .zip(black_box(&rhs))
                .zip(&mut product)
            {
                *out = f8::from_bits(
                    (f64::from(a.to_bits()) * f64::from(b.to_bits()) / 255.0).round_ties_even()
                        as u8,
                );
            }
            black_box(&product);
        });
    }
}