simdjson-rust 0.4.0-alpha.2

Rust bindings for the simdjson project.
Documentation
use criterion::{Criterion, black_box, criterion_group, criterion_main};
use serde::{Deserialize, Serialize};
use simdjson_rust::{dom::Parser, serde::from_tape};

/// Deserialize `T` from `json` via the `simd-json` crate's serde path.
///
/// `simd_json::serde::from_str` is unsafe: it rewrites its `&mut str` input
/// in place (using it as a padded scratch buffer), leaving it as non-UTF-8
/// bytes, so each iteration must hand it a fresh owned copy. The clone cost
/// is identical for every size and is not specific to any parser; it is a
/// constant tax on this bench function alone, so read the simd-json numbers
/// as "parse + one String clone".
fn simd_json_crate_struct<T: for<'de> Deserialize<'de>>(json: &str) -> T {
    let mut buf = json.to_string();
    // SAFETY: `buf` is a fresh owned copy not referenced elsewhere; the
    // function leaves it as non-UTF-8, but we drop it immediately after.
    unsafe { simd_json::serde::from_str(&mut buf).unwrap() }
}

// ---------------------------------------------------------------------------
// Struct Definitions for Benchmarking
// ---------------------------------------------------------------------------

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct SmallData {
    x: f64,
    y: f64,
    label: String,
    valid: bool,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct UserProfile {
    id: u64,
    name: String,
    email: String,
    active: bool,
    friends: Vec<u64>,
    score: f64,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
struct LargeData {
    users: Vec<UserProfile>,
    version: String,
    timestamp: u64,
}

// ---------------------------------------------------------------------------
// Helper generators
// ---------------------------------------------------------------------------

fn make_small_data() -> (SmallData, String) {
    let data = SmallData {
        x: 12.34,
        y: -56.78,
        label: "Benchmark Coordinates".to_string(),
        valid: true,
    };
    let json = serde_json::to_string(&data).unwrap();
    (data, json)
}

fn make_medium_data() -> (UserProfile, String) {
    let data = UserProfile {
        id: 998877,
        name: "John Doe".to_string(),
        email: "john.doe@example.com".to_string(),
        active: true,
        friends: (0..50).map(|i| 1000 + i).collect(),
        score: 98.76,
    };
    let json = serde_json::to_string(&data).unwrap();
    (data, json)
}

fn make_large_data() -> (LargeData, String) {
    let users = (0..500)
        .map(|id| UserProfile {
            id,
            name: format!("User Name {}", id),
            email: format!("user.{}@example.com", id),
            active: id % 2 == 0,
            friends: (0..(id % 20)).map(|i| 10000 + i).collect(),
            score: (id as f64) * 1.5,
        })
        .collect();

    let data = LargeData {
        users,
        version: "v1.0.0-benchmark".to_string(),
        timestamp: 1719999999,
    };
    let json = serde_json::to_string(&data).unwrap();
    (data, json)
}

// ---------------------------------------------------------------------------
// Benchmarks
// ---------------------------------------------------------------------------

fn bench_small(c: &mut Criterion) {
    let (_, json) = make_small_data();
    let mut parser = Parser::default();

    let mut group = c.benchmark_group("Small JSON (~100B)");

    group.bench_function("serde_json_deserialize_struct", |b| {
        b.iter(|| {
            let res: SmallData = serde_json::from_str(black_box(&json)).unwrap();
            black_box(res);
        })
    });

    group.bench_function("simd_json_deserialize_struct", |b| {
        b.iter(|| {
            let res: SmallData = simd_json_crate_struct(black_box(&json));
            black_box(res);
        })
    });

    group.bench_function("simdjson_rust_deserialize_struct_reuse", |b| {
        b.iter(|| {
            let tape = parser.parse_str(black_box(&json)).unwrap();
            let res: SmallData = from_tape(tape).unwrap();
            black_box(res);
        })
    });

    group.finish();
}

fn bench_medium(c: &mut Criterion) {
    let (_, json) = make_medium_data();
    let mut parser = Parser::default();

    let mut group = c.benchmark_group("Medium JSON (~2KB)");

    group.bench_function("serde_json_deserialize_struct", |b| {
        b.iter(|| {
            let res: UserProfile = serde_json::from_str(black_box(&json)).unwrap();
            black_box(res);
        })
    });

    group.bench_function("simd_json_deserialize_struct", |b| {
        b.iter(|| {
            let res: UserProfile = simd_json_crate_struct(black_box(&json));
            black_box(res);
        })
    });

    group.bench_function("simdjson_rust_deserialize_struct_reuse", |b| {
        b.iter(|| {
            let tape = parser.parse_str(black_box(&json)).unwrap();
            let res: UserProfile = from_tape(tape).unwrap();
            black_box(res);
        })
    });

    group.finish();
}

fn bench_large(c: &mut Criterion) {
    let (_, json) = make_large_data();
    let mut parser = Parser::default();

    let mut group = c.benchmark_group("Large JSON (~100KB)");

    group.bench_function("serde_json_deserialize_struct", |b| {
        b.iter(|| {
            let res: LargeData = serde_json::from_str(black_box(&json)).unwrap();
            black_box(res);
        })
    });

    group.bench_function("simd_json_deserialize_struct", |b| {
        b.iter(|| {
            let res: LargeData = simd_json_crate_struct(black_box(&json));
            black_box(res);
        })
    });

    group.bench_function("simdjson_rust_deserialize_struct_reuse", |b| {
        b.iter(|| {
            let tape = parser.parse_str(black_box(&json)).unwrap();
            let res: LargeData = from_tape(tape).unwrap();
            black_box(res);
        })
    });

    group.finish();
}

criterion_group!(benches, bench_small, bench_medium, bench_large);
criterion_main!(benches);