cloudini 0.3.2

The cloudini point cloud compression library for Rust.
Documentation
//! Criterion benchmarks: Rust implementation vs C++ implementation (feature `use_cpp`).
//!
//! Run the Rust-only benchmarks:
//!   cargo bench --bench compress_bench --no-default-features
//!
//! Run with C++ comparison:
//!   cargo bench --bench compress_bench --features use_cpp --no-default-features

use criterion::{BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};

use cloudini::{
    CompressionOption, EncodingInfo, EncodingOptions, FieldType, PointField, PointcloudDecoder,
    PointcloudEncoder,
};

// ── shared helpers ────────────────────────────────────────────────────────────

fn make_xyzi_info(n_points: u32, comp: CompressionOption) -> EncodingInfo {
    EncodingInfo {
        fields: vec![
            PointField {
                name: "x".into(),
                offset: 0,
                field_type: FieldType::Float32,
                resolution: Some(0.001),
            },
            PointField {
                name: "y".into(),
                offset: 4,
                field_type: FieldType::Float32,
                resolution: Some(0.001),
            },
            PointField {
                name: "z".into(),
                offset: 8,
                field_type: FieldType::Float32,
                resolution: Some(0.001),
            },
            PointField {
                name: "intensity".into(),
                offset: 12,
                field_type: FieldType::Uint8,
                resolution: None,
            },
        ],
        width: n_points,
        height: 1,
        point_step: 13,
        encoding_opt: EncodingOptions::Lossy,
        compression_opt: comp,
        ..EncodingInfo::default()
    }
}

/// Generate a realistic lidar-like point cloud (sine-wave spiral).
fn gen_cloud(n: usize) -> Vec<u8> {
    let mut data = vec![0u8; n * 13];
    for i in 0..n {
        let t = i as f32 * 0.005;
        let x = t.sin() * 20.0 + (t * 0.3).cos() * 5.0;
        let y = t.cos() * 15.0 + (t * 0.7).sin() * 3.0;
        let z = (t * 0.2).sin() * 8.0 + 1.0;
        let intensity = (i % 256) as u8;
        let base = i * 13;
        data[base..base + 4].copy_from_slice(&x.to_le_bytes());
        data[base + 4..base + 8].copy_from_slice(&y.to_le_bytes());
        data[base + 8..base + 12].copy_from_slice(&z.to_le_bytes());
        data[base + 12] = intensity;
    }
    data
}

// ── encode benchmarks ─────────────────────────────────────────────────────────

fn bench_encode_lz4(c: &mut Criterion) {
    let mut group = c.benchmark_group("encode/lz4-lossy");

    for &n in &[1_000usize, 10_000, 100_000] {
        let cloud = gen_cloud(n);
        let info = make_xyzi_info(n as u32, CompressionOption::Lz4);

        group.throughput(Throughput::Bytes(cloud.len() as u64));

        // ── Rust ──
        group.bench_with_input(BenchmarkId::new("rust", n), &cloud, |b, cloud| {
            let enc = PointcloudEncoder::new(info.clone());
            b.iter(|| enc.encode(cloud).unwrap())
        });

        // ── Rust multi-threaded ──
        #[cfg(feature = "parallel")]
        group.bench_with_input(BenchmarkId::new("rust-mt", n), &cloud, |b, cloud| {
            let enc = PointcloudEncoder::new(info.clone()).with_threads(true);
            b.iter(|| enc.encode(cloud).unwrap())
        });

        // ── C++ (single-threaded, for apples-to-apples comparison) ──
        #[cfg(feature = "use_cpp")]
        group.bench_with_input(BenchmarkId::new("cpp-st", n), &cloud, |b, cloud| {
            let enc = cloudini::CppPointcloudEncoder::new(info.clone()).with_threads(false);
            b.iter(|| enc.encode(cloud).unwrap())
        });

        // ── C++ (multi-threaded, real-world default) ──
        #[cfg(feature = "use_cpp")]
        group.bench_with_input(BenchmarkId::new("cpp-mt", n), &cloud, |b, cloud| {
            let enc = cloudini::CppPointcloudEncoder::new(info.clone()).with_threads(true);
            b.iter(|| enc.encode(cloud).unwrap())
        });
    }

    group.finish();
}

fn bench_encode_zstd(c: &mut Criterion) {
    let mut group = c.benchmark_group("encode/zstd-lossy");

    for &n in &[1_000usize, 10_000, 100_000] {
        let cloud = gen_cloud(n);
        let info = make_xyzi_info(n as u32, CompressionOption::Zstd);

        group.throughput(Throughput::Bytes(cloud.len() as u64));

        // ── Rust ──
        group.bench_with_input(BenchmarkId::new("rust", n), &cloud, |b, cloud| {
            let enc = PointcloudEncoder::new(info.clone());
            b.iter(|| enc.encode(cloud).unwrap())
        });

        // ── Rust multi-threaded ──
        #[cfg(feature = "parallel")]
        group.bench_with_input(BenchmarkId::new("rust-mt", n), &cloud, |b, cloud| {
            let enc = PointcloudEncoder::new(info.clone()).with_threads(true);
            b.iter(|| enc.encode(cloud).unwrap())
        });

        // ── C++ single-threaded ──
        #[cfg(feature = "use_cpp")]
        group.bench_with_input(BenchmarkId::new("cpp-st", n), &cloud, |b, cloud| {
            let enc = cloudini::CppPointcloudEncoder::new(info.clone()).with_threads(false);
            b.iter(|| enc.encode(cloud).unwrap())
        });

        // ── C++ multi-threaded ──
        #[cfg(feature = "use_cpp")]
        group.bench_with_input(BenchmarkId::new("cpp-mt", n), &cloud, |b, cloud| {
            let enc = cloudini::CppPointcloudEncoder::new(info.clone()).with_threads(true);
            b.iter(|| enc.encode(cloud).unwrap())
        });
    }

    group.finish();
}

// ── decode benchmarks ─────────────────────────────────────────────────────────

fn bench_decode_lz4(c: &mut Criterion) {
    let mut group = c.benchmark_group("decode/lz4-lossy");

    for &n in &[1_000usize, 10_000, 100_000] {
        let cloud = gen_cloud(n);
        let info = make_xyzi_info(n as u32, CompressionOption::Lz4);

        // Pre-compress with the Rust encoder (the wire format is the same either way).
        let compressed = PointcloudEncoder::new(info.clone()).encode(&cloud).unwrap();

        group.throughput(Throughput::Bytes(cloud.len() as u64));

        // ── Rust ──
        group.bench_with_input(BenchmarkId::new("rust", n), &compressed, |b, compressed| {
            let dec = PointcloudDecoder::new();
            b.iter(|| dec.decode(compressed).unwrap())
        });

        // ── Rust multi-threaded ──
        #[cfg(feature = "parallel")]
        group.bench_with_input(
            BenchmarkId::new("rust-mt", n),
            &compressed,
            |b, compressed| {
                let dec = PointcloudDecoder::new().with_threads(true);
                b.iter(|| dec.decode(compressed).unwrap())
            },
        );

        // ── C++ ──
        #[cfg(feature = "use_cpp")]
        group.bench_with_input(BenchmarkId::new("cpp", n), &compressed, |b, compressed| {
            let dec = cloudini::CppPointcloudDecoder::new();
            b.iter(|| dec.decode(compressed).unwrap())
        });
    }

    group.finish();
}

fn bench_decode_zstd(c: &mut Criterion) {
    let mut group = c.benchmark_group("decode/zstd-lossy");

    for &n in &[1_000usize, 10_000, 100_000] {
        let cloud = gen_cloud(n);
        let info = make_xyzi_info(n as u32, CompressionOption::Zstd);

        let compressed = PointcloudEncoder::new(info.clone()).encode(&cloud).unwrap();

        group.throughput(Throughput::Bytes(cloud.len() as u64));

        // ── Rust ──
        group.bench_with_input(BenchmarkId::new("rust", n), &compressed, |b, compressed| {
            let dec = PointcloudDecoder::new();
            b.iter(|| dec.decode(compressed).unwrap())
        });

        // ── Rust multi-threaded ──
        #[cfg(feature = "parallel")]
        group.bench_with_input(
            BenchmarkId::new("rust-mt", n),
            &compressed,
            |b, compressed| {
                let dec = PointcloudDecoder::new().with_threads(true);
                b.iter(|| dec.decode(compressed).unwrap())
            },
        );

        // ── C++ ──
        #[cfg(feature = "use_cpp")]
        group.bench_with_input(BenchmarkId::new("cpp", n), &compressed, |b, compressed| {
            let dec = cloudini::CppPointcloudDecoder::new();
            b.iter(|| dec.decode(compressed).unwrap())
        });
    }

    group.finish();
}

// ── criterion entry point ─────────────────────────────────────────────────────

criterion_group!(
    benches,
    bench_encode_lz4,
    bench_encode_zstd,
    bench_decode_lz4,
    bench_decode_zstd,
);
criterion_main!(benches);