// __ _____ _____ _____ // __| | __| | | | JSON for Modern C++ (supporting code) // | | |__ | | | | | | version 3.12.0 // |_____|_____|_____|_|___| https://github.com/nlohmann/json // // SPDX-FileCopyrightText: 2013-2026 Niels Lohmann // SPDX-License-Identifier: MIT // The Rust counterpart of bench_all.cpp: the same use cases (validate, sum, select, edit, minify, write), files, // checks (right result; validation level from malformed-input probes) and timing method (every variant interleaved in every round, each // timed call after an untimed one, best round); see README.md. The input is the file as bytes (&[u8]); simd-json // parses in place, so its variants copy the input inside the timed call. // // Usage: bench_rs [rounds] [document] [use case] use serde::de::{DeserializeOwned, DeserializeSeed, IgnoredAny, MapAccess, SeqAccess, Visitor}; use simd_json::prelude::*; use sonic_rs::{JsonContainerTrait, JsonValueMutTrait, JsonValueTrait}; use std::cell::RefCell; use std::panic::{catch_unwind, AssertUnwindSafe}; use std::time::Instant; #[derive(Default, Debug, Clone, Copy)] struct Stats { num: f64, str_: usize, nodes: usize, } // ---- sum: walk a DOM, visit every value, convert every number, touch every string and key fn walk_simd(v: &simd_json::BorrowedValue, st: &mut Stats) { st.nodes += 1; if let Some(o) = v.as_object() { for (k, x) in o.iter() { st.str_ += k.len(); walk_simd(x, st); } } else if let Some(a) = v.as_array() { for x in a.iter() { walk_simd(x, st); } } else if let Some(s) = v.as_str() { st.str_ += s.len(); } else if v.is_u64() { st.num += v.as_u64().unwrap() as f64; } else if v.is_i64() { st.num += v.as_i64().unwrap() as f64; } else if v.is_f64() { st.num += v.as_f64().unwrap(); } } fn walk_sonic(v: &sonic_rs::Value, st: &mut Stats) { st.nodes += 1; if let Some(o) = v.as_object() { for (k, x) in o.iter() { st.str_ += k.len(); walk_sonic(x, st); } } else if let Some(a) = v.as_array() { for x in a.iter() { walk_sonic(x, st); } } else if let Some(s) = v.as_str() { st.str_ += s.len(); } else if v.is_number() { if let Some(u) = v.as_u64() { st.num += u as f64; } else if let Some(i) = v.as_i64() { st.num += i as f64; } else { st.num += v.as_f64().unwrap(); } } } fn walk_serde(v: &serde_json::Value, st: &mut Stats) { st.nodes += 1; match v { serde_json::Value::Object(o) => { for (k, x) in o.iter() { st.str_ += k.len(); walk_serde(x, st); } } serde_json::Value::Array(a) => { for x in a.iter() { walk_serde(x, st); } } serde_json::Value::String(s) => st.str_ += s.len(), serde_json::Value::Number(n) => { if let Some(u) = n.as_u64() { st.num += u as f64; } else if let Some(i) = n.as_i64() { st.num += i as f64; } else { st.num += n.as_f64().unwrap(); } } _ => {} } } // the same visit without a DOM: a serde visitor, driven by any serde deserializer, in document order struct Sum<'a>(&'a mut Stats); struct Key<'a>(&'a mut Stats); impl<'de> DeserializeSeed<'de> for Sum<'_> { type Value = (); fn deserialize>(self, d: D) -> Result<(), D::Error> { d.deserialize_any(self) } } impl<'de> Visitor<'de> for Sum<'_> { type Value = (); fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str("a JSON value") } fn visit_unit(self) -> Result<(), E> { self.0.nodes += 1; Ok(()) } fn visit_bool(self, _: bool) -> Result<(), E> { self.0.nodes += 1; Ok(()) } fn visit_i64(self, v: i64) -> Result<(), E> { self.0.nodes += 1; self.0.num += v as f64; Ok(()) } fn visit_u64(self, v: u64) -> Result<(), E> { self.0.nodes += 1; self.0.num += v as f64; Ok(()) } fn visit_f64(self, v: f64) -> Result<(), E> { self.0.nodes += 1; self.0.num += v; Ok(()) } fn visit_str(self, v: &str) -> Result<(), E> { self.0.nodes += 1; self.0.str_ += v.len(); Ok(()) } fn visit_seq>(self, mut a: A) -> Result<(), A::Error> { let st = self.0; st.nodes += 1; while a.next_element_seed(Sum(&mut *st))?.is_some() {} Ok(()) } fn visit_map>(self, mut a: A) -> Result<(), A::Error> { let st = self.0; st.nodes += 1; while a.next_key_seed(Key(&mut *st))?.is_some() { a.next_value_seed(Sum(&mut *st))?; } Ok(()) } } impl<'de> DeserializeSeed<'de> for Key<'_> { type Value = (); fn deserialize>(self, d: D) -> Result<(), D::Error> { d.deserialize_str(self) } } impl<'de> Visitor<'de> for Key<'_> { type Value = (); fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str("a key") } fn visit_str(self, v: &str) -> Result<(), E> { self.0.str_ += v.len(); Ok(()) } } fn sum_serde(s: &[u8], st: &mut Stats) -> bool { let mut de = serde_json::Deserializer::from_slice(s); Sum(st).deserialize(&mut de).is_ok() && de.end().is_ok() } fn sum_sonic(s: &[u8], st: &mut Stats) -> bool { let mut de = sonic_rs::Deserializer::from_slice(s); Sum(st).deserialize(&mut de).is_ok() && de.end().is_ok() } fn sum_simd(s: &[u8], st: &mut Stats) -> bool { let mut b = s.to_vec(); match simd_json::Deserializer::from_slice(&mut b) { Ok(mut de) => Sum(st).deserialize(&mut de).is_ok(), Err(_) => false, } } // ---- select: read a few fields of every record (the fields of bench_view.cpp) // on a DOM; serde_json's is the reference macro_rules! pick_value { ($fname:ident, $t:ty) => { fn $fname(name: &str, r: &$t) -> f64 { fn size(v: &$t) -> usize { if let Some(a) = v.as_array() { a.len() } else { v.as_object().unwrap().len() } } fn status(acc: &mut f64, s: &$t) { *acc += s["id"].as_u64().unwrap() as f64; *acc += s["user"]["screen_name"].as_str().unwrap().len() as f64; *acc += s["retweet_count"].as_i64().unwrap() as f64; } let mut acc = 0.0; match name { "twitter" => { for s in r["statuses"].as_array().unwrap().iter() { status(&mut acc, s); } } "status" => status(&mut acc, r), "citm_catalog" => { for p in r["performances"].as_array().unwrap().iter() { acc += (p["id"].as_u64().unwrap() + p["eventId"].as_u64().unwrap() + size(&p["seatCategories"]) as u64) as f64; } acc += size(&r["events"]) as f64; } "canada" => { let g = &r["features"][0]["geometry"]; acc += (r["type"].as_str().unwrap().len() + g["type"].as_str().unwrap().len() + size(&g["coordinates"])) as f64; } "jeopardy" => { for q in r.as_array().unwrap().iter() { acc += if q["round"].as_str().unwrap() == "Final Jeopardy!" { 1.0 } else { 0.0 }; acc += q["category"].as_str().unwrap().len() as f64; } } _ => {} } acc } }; } pick_value!(pick_serde, serde_json::Value); pick_value!(pick_sonic, sonic_rs::Value); pick_value!(pick_simd, simd_json::BorrowedValue<'_>); // into structs that declare only the fields the selection uses; unknown keys are skipped mod sel { use serde::de::IgnoredAny; use serde::Deserialize; use std::collections::HashMap; #[derive(Deserialize)] pub struct User { pub screen_name: String, } #[derive(Deserialize)] pub struct Status { pub id: u64, pub user: User, pub retweet_count: i64, } #[derive(Deserialize)] pub struct Twitter { pub statuses: Vec, } #[derive(Deserialize)] pub struct Performance { pub id: u64, #[serde(rename = "eventId")] pub event_id: u64, #[serde(rename = "seatCategories")] pub seat_categories: Vec, } #[derive(Deserialize)] pub struct Citm { pub events: HashMap, pub performances: Vec, } #[derive(Deserialize)] pub struct Geometry { #[serde(rename = "type")] pub type_: String, pub coordinates: Vec, } #[derive(Deserialize)] pub struct Feature { pub geometry: Geometry, } #[derive(Deserialize)] pub struct Canada { #[serde(rename = "type")] pub type_: String, pub features: Vec, } #[derive(Deserialize)] pub struct Question { pub category: String, pub round: String, } } trait De { fn de(s: &[u8]) -> Option; } struct SerdeJson; struct SerdeJsonStr; // checks UTF-8 with std::str::from_utf8 first, then reads from the &str struct SonicRs; struct SimdJson; impl De for SerdeJson { fn de(s: &[u8]) -> Option { serde_json::from_slice(s).ok() } } impl De for SerdeJsonStr { fn de(s: &[u8]) -> Option { serde_json::from_str(std::str::from_utf8(s).ok()?).ok() } } impl De for SonicRs { fn de(s: &[u8]) -> Option { sonic_rs::from_slice(s).ok() } } impl De for SimdJson { fn de(s: &[u8]) -> Option { let mut b = s.to_vec(); simd_json::serde::from_slice(&mut b).ok() } } fn pick_structs(name: &str, s: &[u8]) -> Option { fn status(acc: &mut f64, x: &sel::Status) { *acc += x.id as f64; *acc += x.user.screen_name.len() as f64; *acc += x.retweet_count as f64; } let mut acc = 0.0; match name { "twitter" => { let t: sel::Twitter = D::de(s)?; for x in &t.statuses { status(&mut acc, x); } } "status" => { let x: sel::Status = D::de(s)?; status(&mut acc, &x); } "citm_catalog" => { let c: sel::Citm = D::de(s)?; for p in &c.performances { acc += (p.id + p.event_id + p.seat_categories.len() as u64) as f64; } acc += c.events.len() as f64; } "canada" => { let c: sel::Canada = D::de(s)?; let g = &c.features[0].geometry; acc += (c.type_.len() + g.type_.len() + g.coordinates.len()) as f64; } "jeopardy" => { let q: Vec = D::de(s)?; for x in &q { acc += if x.round == "Final Jeopardy!" { 1.0 } else { 0.0 }; acc += x.category.len() as f64; } } _ => {} } Some(acc) } // ---- edit: the same edits as bench_all.cpp, then write compact JSON fn edit_serde(name: &str, r: &mut serde_json::Value) { fn status(s: &mut serde_json::Value) { let o = s.as_object_mut().unwrap(); let rc = o["retweet_count"].as_i64().unwrap(); o.insert("retweet_count".to_string(), (rc + 1).into()); o.remove("entities"); o.insert("seen".to_string(), true.into()); } match name { "twitter" => { for s in r["statuses"].as_array_mut().unwrap() { status(s); } } "status" => status(r), "citm_catalog" => { for p in r["performances"].as_array_mut().unwrap() { let o = p.as_object_mut().unwrap(); let start = o["start"].as_i64().unwrap(); o.insert("start".to_string(), (start + 1).into()); o.remove("seatMapImage"); o.insert("seen".to_string(), true.into()); } } "canada" => { r["features"][0]["properties"]["name"] = "Kanada".into(); for f in r["features"].as_array_mut().unwrap() { f["geometry"]["coordinates"][0].as_array_mut().unwrap().push(serde_json::json!([0.5, 0.25])); } } "jeopardy" => { for q in r.as_array_mut().unwrap() { let o = q.as_object_mut().unwrap(); o.insert("value".to_string(), serde_json::Value::Null); o.remove("air_date"); o.insert("seen".to_string(), true.into()); } } _ => {} } } fn edit_sonic(name: &str, r: &mut sonic_rs::Value) { fn status(s: &mut sonic_rs::Value) { let o = s.as_object_mut().unwrap(); let rc = o.get(&"retweet_count").unwrap().as_i64().unwrap(); o.insert("retweet_count", rc + 1); o.remove(&"entities"); o.insert("seen", true); } match name { "twitter" => { for s in r.get_mut("statuses").unwrap().as_array_mut().unwrap().iter_mut() { status(s); } } "status" => status(r), "citm_catalog" => { for p in r.get_mut("performances").unwrap().as_array_mut().unwrap().iter_mut() { let o = p.as_object_mut().unwrap(); let start = o.get(&"start").unwrap().as_i64().unwrap(); o.insert("start", start + 1); o.remove(&"seatMapImage"); o.insert("seen", true); } } "canada" => { let fs = r.get_mut("features").unwrap().as_array_mut().unwrap(); fs[0].get_mut("properties").unwrap().as_object_mut().unwrap().insert("name", "Kanada"); for f in fs.iter_mut() { let ring = f.get_mut("geometry").unwrap().get_mut("coordinates").unwrap().get_mut(0).unwrap(); ring.as_array_mut().unwrap().push(sonic_rs::json!([0.5, 0.25])); } } "jeopardy" => { for q in r.as_array_mut().unwrap().iter_mut() { let o = q.as_object_mut().unwrap(); o.insert("value", sonic_rs::Value::new_null()); o.remove(&"air_date"); o.insert("seen", true); } } _ => {} } } macro_rules! edit_simd { ($fname:ident, $t:ty) => { fn $fname(name: &str, r: &mut $t) { fn status(s: &mut $t) { let o = s.as_object_mut().unwrap(); let rc = o.get("retweet_count").unwrap().as_i64().unwrap(); o.insert("retweet_count".into(), (rc + 1).into()); o.remove("entities"); o.insert("seen".into(), true.into()); } match name { "twitter" => { for s in r.as_object_mut().unwrap().get_mut("statuses").unwrap().as_array_mut().unwrap().iter_mut() { status(s); } } "status" => status(r), "citm_catalog" => { for p in r.as_object_mut().unwrap().get_mut("performances").unwrap().as_array_mut().unwrap().iter_mut() { let o = p.as_object_mut().unwrap(); let start = o.get("start").unwrap().as_i64().unwrap(); o.insert("start".into(), (start + 1).into()); o.remove("seatMapImage"); o.insert("seen".into(), true.into()); } } "canada" => { let fs = r.as_object_mut().unwrap().get_mut("features").unwrap().as_array_mut().unwrap(); fs[0].as_object_mut().unwrap().get_mut("properties").unwrap().as_object_mut().unwrap().insert("name".into(), "Kanada".into()); for f in fs.iter_mut() { let g = f.as_object_mut().unwrap().get_mut("geometry").unwrap().as_object_mut().unwrap(); let ring = &mut g.get_mut("coordinates").unwrap().as_array_mut().unwrap()[0]; ring.as_array_mut().unwrap().push(vec![<$t>::from(0.5), <$t>::from(0.25)].into()); } } "jeopardy" => { for q in r.as_array_mut().unwrap().iter_mut() { let o = q.as_object_mut().unwrap(); o.insert("value".into(), <$t>::Static(simd_json::StaticNode::Null)); o.remove("air_date"); o.insert("seen".into(), true.into()); } } _ => {} } } }; } edit_simd!(edit_simd_owned, simd_json::OwnedValue); edit_simd!(edit_simd_borrowed, simd_json::BorrowedValue<'_>); // ---- driver struct Res { st: Stats, acc: f64, want: bool, out: Vec, } impl Res { fn new(want: bool) -> Res { Res { st: Stats::default(), acc: 0.0, want, out: Vec::new() } } } fn emit(r: &mut Res, o: Vec) -> bool { r.acc = o.len() as f64; if r.want { r.out = o; } true } struct Variant<'a> { name: &'static str, f: Box bool + 'a>, any_order: bool, // sum: visits objects in another order than the document's } fn v<'a>(name: &'static str, f: impl Fn(&[u8], &mut Res) -> bool + 'a) -> Variant<'a> { Variant { name, f: Box::new(f), any_order: false } } fn v_any<'a>(name: &'static str, f: impl Fn(&[u8], &mut Res) -> bool + 'a) -> Variant<'a> { Variant { name, f: Box::new(f), any_order: true } } fn run(var: &Variant, s: &[u8], r: &mut Res) -> bool { catch_unwind(AssertUnwindSafe(|| (var.f)(s, r))).unwrap_or(false) } static mut SINK: f64 = 0.0; fn sink(x: f64) { unsafe { std::ptr::write_volatile(std::ptr::addr_of_mut!(SINK), x) } } // the input contract of bench_all.cpp: at least 64 bytes of spare capacity fn padded(s: &[u8]) -> Vec { let mut r = Vec::with_capacity(s.len() + 64); r.extend_from_slice(s); r } // malformed inputs derived from a valid document, as in bench_all.cpp (0xFF in a string value, not a key) // (what, text, is the 0xFF probe) fn malformed(s: &[u8]) -> Vec<(&'static str, Vec, bool)> { let (mut first_close, mut mid_quote, mut last_quote) = (None, None, None); let mut in_string = false; let mut stack: Vec = Vec::new(); let mut prev = 0u8; let mut i = 0; while i < s.len() { let ch = s[i]; if in_string { if ch == b'\\' { i += 1; } else if ch == b'"' { in_string = false; } } else if ch == b'"' { in_string = true; let value = prev == b':' || (stack.last() == Some(&b'[') && (prev == b'[' || prev == b',')); if value { last_quote = Some(i); if mid_quote.is_none() && i >= s.len() / 2 { mid_quote = Some(i); } } prev = b'"'; } else if ch == b'{' || ch == b'[' { stack.push(ch); prev = ch; } else if ch == b'}' || ch == b']' { if ch == b'}' && first_close.is_none() { first_close = Some(i); } stack.pop(); prev = ch; } else if ch == b':' || ch == b',' { prev = ch; } i += 1; } let mut t = s.to_vec(); while matches!(t.last(), Some(b' ' | b'\n' | b'\r' | b'\t')) { t.pop(); } t.pop(); let mut out = vec![("truncated input", padded(&t), false)]; let mut t = s.to_vec(); t.insert(first_close.unwrap(), b','); out.push(("a stray comma before '}'", padded(&t), false)); let mut t = s.to_vec(); t.insert(mid_quote.or(last_quote).unwrap() + 1, 0xFF); out.push(("invalid UTF-8 (0xFF in a string)", padded(&t), true)); out } fn same_text(out: &[u8], expected: &serde_json::Value) -> bool { serde_json::from_slice::(out).map(|v| v == *expected).unwrap_or(false) } fn main() { let args: Vec = std::env::args().collect(); if args.len() < 2 { eprintln!("usage: {} [rounds] [document] [use case]", args[0]); std::process::exit(1); } let t = format!("{}/", args[1]); let rounds: usize = args.get(2).map(|r| r.parse().unwrap()).unwrap_or(30).max(1); let only: String = args.get(3).cloned().unwrap_or_default(); let only_uc: String = args.get(4).cloned().unwrap_or_default(); let mut docs: Vec<(String, Vec, usize)> = Vec::new(); for f in ["nativejson-benchmark/twitter.json", "nativejson-benchmark/citm_catalog.json", "nativejson-benchmark/canada.json", "jeopardy/jeopardy.json"] { let name = f.split('/').nth(1).unwrap().trim_end_matches(".json").to_string(); docs.push((name, padded(&std::fs::read(format!("{}{}", t, f)).unwrap()), 1)); } let tw: serde_json::Value = serde_json::from_slice(&docs[0].1).unwrap(); docs.push(("status".to_string(), padded(serde_json::to_string(&tw["statuses"][0]).unwrap().as_bytes()), 200)); std::panic::set_hook(Box::new(|_| {})); // a panic is a failed call, reported by the checks let simd_reused = RefCell::new((simd_json::Buffers::default(), Vec::::new())); let mut excluded_total = 0; for (name, s, batch) in &docs { if !only.is_empty() && *name != only { continue; } let name = name.as_str(); let s = s.as_slice(); let bad = malformed(s); for uc in ["validate", "sum", "select", "edit", "minify", "write"] { if !only_uc.is_empty() && uc != only_uc { continue; } // write: each library's document, parsed once outside the timer let mut wbuf: Vec; let (w_serde, w_sonic, w_simd) = if uc == "write" { wbuf = s.to_vec(); ( Some(serde_json::from_slice::(s).unwrap()), Some(sonic_rs::from_slice::(s).unwrap()), Some(simd_json::to_borrowed_value(&mut wbuf).unwrap()), ) } else { (None, None, None) }; let reuse = &simd_reused; let all: Vec = match uc { "validate" => vec![ v("serde_json: IgnoredAny", |s, _| serde_json::from_slice::(s).is_ok()), v("serde_json: from_utf8 + IgnoredAny", |s, _| std::str::from_utf8(s).ok().map_or(false, |t| serde_json::from_str::(t).is_ok())), v("serde_json: Value", |s, _| serde_json::from_slice::(s).is_ok()), v("sonic-rs: IgnoredAny", |s, _| sonic_rs::from_slice::(s).is_ok()), v("sonic-rs: Value", |s, _| sonic_rs::from_slice::(s).is_ok()), v("simd-json: tape (copy)", |s, _| { let mut b = s.to_vec(); simd_json::to_tape(&mut b).is_ok() }), v("simd-json: borrowed (copy)", |s, _| { let mut b = s.to_vec(); let ok = simd_json::to_borrowed_value(&mut b).is_ok(); ok }), v("simd-json: tape (reused)", move |s, _| { let mut g = reuse.borrow_mut(); let (ref mut bufs, ref mut b) = *g; b.clear(); b.extend_from_slice(s); simd_json::to_tape_with_buffers(b, bufs).is_ok() }), ], "sum" => vec![ v("serde_json: visitor", |s, r| sum_serde(s, &mut r.st)), v_any("serde_json: Value", |s, r| serde_json::from_slice::(s).map(|x| walk_serde(&x, &mut r.st)).is_ok()), v("sonic-rs: visitor", |s, r| sum_sonic(s, &mut r.st)), v("sonic-rs: Value", |s, r| sonic_rs::from_slice::(s).map(|x| walk_sonic(&x, &mut r.st)).is_ok()), v("simd-json: visitor (copy)", |s, r| sum_simd(s, &mut r.st)), v_any("simd-json: borrowed (copy)", |s, r| { let mut b = s.to_vec(); simd_json::to_borrowed_value(&mut b).map(|x| walk_simd(&x, &mut r.st)).is_ok() }), v_any("simd-json: borrowed (reused)", move |s, r| { let mut g = reuse.borrow_mut(); let (ref mut bufs, ref mut b) = *g; b.clear(); b.extend_from_slice(s); simd_json::to_borrowed_value_with_buffers(b, bufs).map(|x| walk_simd(&x, &mut r.st)).is_ok() }), ], "select" => vec![ v("serde_json: structs", move |s, r| pick_structs::(name, s).map(|a| r.acc = a).is_some()), v("serde_json: from_utf8 + structs", move |s, r| pick_structs::(name, s).map(|a| r.acc = a).is_some()), v("serde_json: Value", move |s, r| serde_json::from_slice::(s).map(|x| r.acc = pick_serde(name, &x)).is_ok()), v("sonic-rs: structs", move |s, r| pick_structs::(name, s).map(|a| r.acc = a).is_some()), v("sonic-rs: Value", move |s, r| sonic_rs::from_slice::(s).map(|x| r.acc = pick_sonic(name, &x)).is_ok()), v("simd-json: structs (copy)", move |s, r| pick_structs::(name, s).map(|a| r.acc = a).is_some()), v("simd-json: borrowed (copy)", move |s, r| { let mut b = s.to_vec(); let ok = simd_json::to_borrowed_value(&mut b).map(|x| r.acc = pick_simd(name, &x)).is_ok(); ok }), ], "edit" => vec![ v("serde_json: Value", move |s, r| match serde_json::from_slice::(s) { Ok(mut x) => { edit_serde(name, &mut x); emit(r, serde_json::to_vec(&x).unwrap()) } Err(_) => false, }), v("sonic-rs: Value", move |s, r| match sonic_rs::from_slice::(s) { Ok(mut x) => { edit_sonic(name, &mut x); emit(r, sonic_rs::to_vec(&x).unwrap()) } Err(_) => false, }), v("simd-json: owned (copy)", move |s, r| { let mut b = s.to_vec(); match simd_json::to_owned_value(&mut b) { Ok(mut x) => { edit_simd_owned(name, &mut x); emit(r, simd_json::to_vec(&x).unwrap()) } Err(_) => false, }}), v("simd-json: borrowed (copy)", move |s, r| { let mut b = s.to_vec(); let ok = match simd_json::to_borrowed_value(&mut b) { Ok(mut x) => { edit_simd_borrowed(name, &mut x); emit(r, simd_json::to_vec(&x).unwrap()) } Err(_) => false, }; ok }), ], "minify" => vec![ v("serde_json: Value", |s, r| serde_json::from_slice::(s).map(|x| emit(r, serde_json::to_vec(&x).unwrap())).unwrap_or(false)), v("sonic-rs: Value", |s, r| sonic_rs::from_slice::(s).map(|x| emit(r, sonic_rs::to_vec(&x).unwrap())).unwrap_or(false)), v("simd-json: borrowed (copy)", |s, r| { let mut b = s.to_vec(); simd_json::to_borrowed_value(&mut b).map(|x| emit(r, simd_json::to_vec(&x).unwrap())).unwrap_or(false) }), ], _ => { let (a, b, c) = (w_serde.as_ref().unwrap(), w_sonic.as_ref().unwrap(), w_simd.as_ref().unwrap()); vec![ v("serde_json: Value", move |_, r| emit(r, serde_json::to_vec(a).unwrap())), v("sonic-rs: Value", move |_, r| emit(r, sonic_rs::to_vec(b).unwrap())), v("simd-json: borrowed", move |_, r| emit(r, simd_json::to_vec(c).unwrap())), ] } }; // the reference result: serde_json's visitor (sum, document order), Value (select, edit, minify, write) let mut ref_st = Stats::default(); let mut ref_acc = 0.0; let mut ref_json = serde_json::Value::Null; match uc { "sum" => { sum_serde(s, &mut ref_st); println!("check {:<13} {:<9} reference: nodes {} str {} num {:.16e}", name, uc, ref_st.nodes, ref_st.str_, ref_st.num); } "select" => { ref_acc = pick_serde(name, &serde_json::from_slice(s).unwrap()); println!("check {:<13} {:<9} reference: {:.16e}", name, uc, ref_acc); } "validate" => {} _ => { ref_json = serde_json::from_slice(s).unwrap(); if uc == "edit" { edit_serde(name, &mut ref_json); } } } let n_all = all.len(); let mut vs: Vec = Vec::new(); let mut levels: Vec<&str> = Vec::new(); for var in all { let mut r = Res::new(true); let mut why = String::new(); let mut panicked = false; if !catch_unwind(AssertUnwindSafe(|| (var.f)(s, &mut r))).unwrap_or_else(|_| { panicked = true; false }) { why = if panicked { "crashes on the valid input" } else { "rejects the valid input" }.to_string(); } else if uc == "sum" && !(r.st.nodes == ref_st.nodes && r.st.str_ == ref_st.str_ && if var.any_order { (r.st.num - ref_st.num).abs() <= 1e-9 * ref_st.num.abs() } else { r.st.num == ref_st.num }) { why = format!("wrong result (nodes {} str {} num {:.16e})", r.st.nodes, r.st.str_, r.st.num); } else if uc == "select" && r.acc != ref_acc { why = format!("wrong result ({:.16e})", r.acc); } else if (uc == "edit" || uc == "minify" || uc == "write") && !same_text(&r.out, &ref_json) { why = "the output does not read back as the expected document".to_string(); } if why.is_empty() { // the validation level from the malformed-input probes, as in bench_all.cpp let mut level = "n/a"; if uc != "write" { let (mut structural, mut utf8) = (false, false); let mut notes: Vec = Vec::new(); for (what, b, is_utf8) in &bad { let mut rb = Res::new(true); match catch_unwind(AssertUnwindSafe(|| (var.f)(b, &mut rb))) { Ok(false) => continue, Ok(true) => { notes.push(format!("accepts {}", what)); if *is_utf8 { utf8 = true } else { structural = true } } Err(_) => { notes.push(format!("crashes on {}", what)); structural = true; } } } level = if structural { "partial" } else if utf8 { "no UTF-8" } else { "full" }; if !notes.is_empty() { println!("level: {} {} | {} | {} | {}", name, uc, var.name, level, notes.join("; ")); } } vs.push(var); levels.push(level); } else { excluded_total += 1; println!("excluded: {} {} | {} | {}", name, uc, var.name, why); } } println!("check {:<13} {:<9} {} of {} variants give the right result", name, uc, vs.len(), n_all); if vs.is_empty() { continue; } let r = if s.len() > 10_000_000 { rounds.min((rounds / 5).max(3)) } else { rounds }; let mut best = vec![f64::MAX; vs.len()]; for _ in 0..r { for (k, var) in vs.iter().enumerate() { let mut res = Res::new(false); run(var, s, &mut res); // untimed: the previous variant's leftovers are cleaned up here sink(res.st.num + res.acc); let t0 = Instant::now(); for _ in 0..*batch { let mut rt = Res::new(false); let ok = run(var, s, &mut rt); sink(if ok { rt.st.num + rt.acc } else { -1.0 }); } let ns = t0.elapsed().as_nanos() as f64 / *batch as f64; best[k] = best[k].min(ns); } } print!("{:<13} {:<9}", name, uc); for (k, var) in vs.iter().enumerate() { print!(" | {} [{}] {:.1}us ({:.2}x)", var.name, levels[k], best[k] / 1e3, best[k] / best[0]); } println!(); } } println!("done: {} variant(s) excluded", excluded_total); }