#[cfg(target_arch = "x86_64")]
use asmjson::parse_to_dom_zmm;
#[cfg(target_arch = "x86_64")]
use asmjson::parse_with_zmm;
use asmjson::sax::Sax;
use asmjson::{DomEntryKind, parse_to_dom};
use criterion::{Criterion, Throughput, criterion_group, criterion_main};
struct LenSumWriter {
total: usize,
}
impl LenSumWriter {
fn new() -> Self {
Self { total: 0 }
}
}
impl<'src> Sax<'src> for LenSumWriter {
type Output = usize;
fn null(&mut self) {}
fn bool_val(&mut self, _v: bool) {}
fn number(&mut self, _s: &'src str) {}
fn string(&mut self, s: &'src str) {
self.total += s.len();
}
fn escaped_string(&mut self, s: &str) {
self.total += s.len();
}
fn key(&mut self, s: &'src str) {
self.total += s.len();
}
fn escaped_key(&mut self, s: &str) {
self.total += s.len();
}
fn start_object(&mut self) {}
fn end_object(&mut self) {}
fn start_array(&mut self) {}
fn end_array(&mut self) {}
fn finish(self) -> Option<usize> {
Some(self.total)
}
}
#[inline]
fn dom_sum_lens(tape: &asmjson::Dom<'_>) -> usize {
tape.entries
.iter()
.map(|e| match e.kind() {
DomEntryKind::String | DomEntryKind::Key => e.as_string().map_or(0, |s| s.len()),
_ => 0,
})
.sum()
}
fn serde_sum_lens(v: &serde_json::Value) -> usize {
match v {
serde_json::Value::String(s) => s.len(),
serde_json::Value::Object(map) => map
.iter()
.map(|(k, val)| k.len() + serde_sum_lens(val))
.sum(),
serde_json::Value::Array(arr) => arr.iter().map(serde_sum_lens).sum(),
_ => 0,
}
}
fn sonic_sum_lens(v: &sonic_rs::Value) -> usize {
use sonic_rs::{JsonContainerTrait, JsonValueTrait};
if let Some(s) = v.as_str() {
return s.len();
}
if let Some(obj) = v.as_object() {
return obj
.iter()
.map(|(k, val): (&str, &sonic_rs::Value)| k.len() + sonic_sum_lens(val))
.sum();
}
if let Some(arr) = v.as_array() {
return arr
.iter()
.map(|val: &sonic_rs::Value| sonic_sum_lens(val))
.sum();
}
0
}
fn simd_sum_lens(v: &simd_json::BorrowedValue<'_>) -> usize {
use simd_json::BorrowedValue;
match v {
BorrowedValue::String(s) => s.len(),
BorrowedValue::Object(map) => map
.iter()
.map(|(k, val)| k.len() + simd_sum_lens(val))
.sum(),
BorrowedValue::Array(arr) => arr.iter().map(simd_sum_lens).sum(),
_ => 0,
}
}
fn gen_string_array(target_bytes: usize) -> String {
let value: String = (b'!'..=b'~') .filter(|&b| b != b'"' && b != b'\\')
.map(|b| b as char)
.cycle()
.take(95)
.collect();
let element = format!(r#""{}""#, value); let elements_needed = target_bytes / (element.len() + 1) + 1;
let mut out = String::with_capacity(target_bytes + 64);
out.push('[');
for i in 0..elements_needed {
if i > 0 {
out.push(',');
}
out.push_str(&element);
}
out.push(']');
out
}
fn gen_string_object(target_bytes: usize) -> String {
let value: String = (b'!'..=b'~')
.filter(|&b| b != b'"' && b != b'\\')
.map(|b| b as char)
.cycle()
.take(85)
.collect();
let members_needed = target_bytes / 102 + 1;
let mut out = String::with_capacity(target_bytes + 64);
out.push('{');
for i in 0..members_needed {
if i > 0 {
out.push(',');
}
out.push_str(&format!(r#""key{:05}":"{}""#, i % 100_000, value));
}
out.push('}');
out
}
fn gen_mixed(target_bytes: usize) -> String {
let tag_a = "alpha";
let tag_b = "beta";
let record_size = 130usize;
let records_needed = target_bytes / record_size + 1;
let mut out = String::with_capacity(target_bytes + 64);
out.push('[');
for i in 0..records_needed {
if i > 0 {
out.push(',');
}
let active = if i % 2 == 0 { "true" } else { "false" };
let score = if i % 3 == 0 {
"null".to_string()
} else {
format!("{}", i / 2)
};
out.push_str(&format!(
r#"{{"id":{i},"name":"item{i}","active":{active},"score":{score},"tags":["{tag_a}","{tag_b}"],"meta":{{"x":{x},"y":{y}}}}}"#,
i = i,
active = active,
score = score,
tag_a = tag_a,
tag_b = tag_b,
x = i % 1000,
y = (i * 7) % 1000,
));
}
out.push(']');
out
}
const TARGET: usize = 10 * 1024 * 1024;
fn bench_string_array(c: &mut Criterion) {
let data = gen_string_array(TARGET);
let mut group = c.benchmark_group("string_array");
group.throughput(Throughput::Bytes(data.len() as u64));
#[cfg(target_arch = "x86_64")]
group.bench_function("asmjson/sax", |b| {
b.iter(|| {
let total = unsafe { parse_with_zmm(&data, LenSumWriter::new()) }.unwrap();
std::hint::black_box(total)
});
});
#[cfg(target_arch = "x86_64")]
group.bench_function("asmjson/dom", |b| {
b.iter(|| {
let tape = unsafe { parse_to_dom_zmm(&data, None) }.unwrap();
std::hint::black_box(dom_sum_lens(&tape))
});
});
group.bench_function("asmjson/u64", |b| {
b.iter(|| {
let tape = parse_to_dom(&data, None).unwrap();
std::hint::black_box(dom_sum_lens(&tape))
});
});
group.bench_function("simd-json", |b| {
b.iter(|| {
let mut bytes = data.as_bytes().to_vec();
let v = simd_json::to_borrowed_value(&mut bytes).unwrap();
std::hint::black_box(simd_sum_lens(&v))
});
});
group.bench_function("sonic-rs", |b| {
b.iter(|| {
let v = sonic_rs::from_str::<sonic_rs::Value>(&data).unwrap();
std::hint::black_box(sonic_sum_lens(&v))
});
});
group.bench_function("serde_json", |b| {
b.iter(|| {
let v = serde_json::from_str::<serde_json::Value>(&data).unwrap();
std::hint::black_box(serde_sum_lens(&v))
});
});
group.finish();
}
fn bench_string_object(c: &mut Criterion) {
let data = gen_string_object(TARGET);
let mut group = c.benchmark_group("string_object");
group.throughput(Throughput::Bytes(data.len() as u64));
#[cfg(target_arch = "x86_64")]
group.bench_function("asmjson/sax", |b| {
b.iter(|| {
let total = unsafe { parse_with_zmm(&data, LenSumWriter::new()) }.unwrap();
std::hint::black_box(total)
});
});
#[cfg(target_arch = "x86_64")]
group.bench_function("asmjson/dom", |b| {
b.iter(|| {
let tape = unsafe { parse_to_dom_zmm(&data, None) }.unwrap();
std::hint::black_box(dom_sum_lens(&tape))
});
});
group.bench_function("asmjson/u64", |b| {
b.iter(|| {
let tape = parse_to_dom(&data, None).unwrap();
std::hint::black_box(dom_sum_lens(&tape))
});
});
group.bench_function("simd-json", |b| {
b.iter(|| {
let mut bytes = data.as_bytes().to_vec();
let v = simd_json::to_borrowed_value(&mut bytes).unwrap();
std::hint::black_box(simd_sum_lens(&v))
});
});
group.bench_function("sonic-rs", |b| {
b.iter(|| {
let v = sonic_rs::from_str::<sonic_rs::Value>(&data).unwrap();
std::hint::black_box(sonic_sum_lens(&v))
});
});
group.bench_function("serde_json", |b| {
b.iter(|| {
let v = serde_json::from_str::<serde_json::Value>(&data).unwrap();
std::hint::black_box(serde_sum_lens(&v))
});
});
group.finish();
}
fn bench_mixed(c: &mut Criterion) {
let data = gen_mixed(TARGET);
let mut group = c.benchmark_group("mixed");
group.throughput(Throughput::Bytes(data.len() as u64));
#[cfg(target_arch = "x86_64")]
group.bench_function("asmjson/sax", |b| {
b.iter(|| {
let total = unsafe { parse_with_zmm(&data, LenSumWriter::new()) }.unwrap();
std::hint::black_box(total)
});
});
#[cfg(target_arch = "x86_64")]
group.bench_function("asmjson/dom", |b| {
b.iter(|| {
let tape = unsafe { parse_to_dom_zmm(&data, None) }.unwrap();
std::hint::black_box(dom_sum_lens(&tape))
});
});
group.bench_function("asmjson/u64", |b| {
b.iter(|| {
let tape = parse_to_dom(&data, None).unwrap();
std::hint::black_box(dom_sum_lens(&tape))
});
});
group.bench_function("simd-json", |b| {
b.iter(|| {
let mut bytes = data.as_bytes().to_vec();
let v = simd_json::to_borrowed_value(&mut bytes).unwrap();
std::hint::black_box(simd_sum_lens(&v))
});
});
group.bench_function("sonic-rs", |b| {
b.iter(|| {
let v = sonic_rs::from_str::<sonic_rs::Value>(&data).unwrap();
std::hint::black_box(sonic_sum_lens(&v))
});
});
group.bench_function("serde_json", |b| {
b.iter(|| {
let v = serde_json::from_str::<serde_json::Value>(&data).unwrap();
std::hint::black_box(serde_sum_lens(&v))
});
});
group.finish();
}
criterion_group!(
benches,
bench_string_array,
bench_string_object,
bench_mixed
);
criterion_main!(benches);