use std::hint::black_box;
use std::time::{Duration, Instant};
use prost::Message as _;
use serde::{Deserialize, Serialize};
use verit::{encode, Dt, Message, Resolver, Schema, SchemaBuilder, SchemaMode, Value};
const N: u16 = 16;
const SET: &[(u16, u64)] = &[
(2, 0xF000_0000_0000_0002),
(7, 0xF000_0000_0000_0007),
(11, 0xF000_0000_0000_000B),
(15, 0xF000_0000_0000_000F),
];
fn veritate_schema(packed: bool) -> Schema {
let fields: Vec<(u16, String, Dt)> =
(1..=N).map(|i| (i, format!("f{i:02}"), Dt::U64)).collect();
let fields_ref: Vec<(u16, &str, Dt)> = fields
.iter()
.map(|(i, n, t)| (*i, n.as_str(), t.clone()))
.collect();
let b = SchemaBuilder::new();
let b = if packed {
b.add_packed_struct("Telemetry", fields_ref)
} else {
b.add_struct("Telemetry", fields_ref)
};
b.build("Telemetry").unwrap()
}
fn veritate_value() -> Value {
Value::Struct(SET.iter().map(|(id, v)| (*id, Value::U64(*v))).collect())
}
#[derive(Clone, PartialEq, prost::Message)]
struct PbTelemetry {
#[prost(uint64, optional, tag = "1")]
f01: Option<u64>,
#[prost(uint64, optional, tag = "2")]
f02: Option<u64>,
#[prost(uint64, optional, tag = "3")]
f03: Option<u64>,
#[prost(uint64, optional, tag = "4")]
f04: Option<u64>,
#[prost(uint64, optional, tag = "5")]
f05: Option<u64>,
#[prost(uint64, optional, tag = "6")]
f06: Option<u64>,
#[prost(uint64, optional, tag = "7")]
f07: Option<u64>,
#[prost(uint64, optional, tag = "8")]
f08: Option<u64>,
#[prost(uint64, optional, tag = "9")]
f09: Option<u64>,
#[prost(uint64, optional, tag = "10")]
f10: Option<u64>,
#[prost(uint64, optional, tag = "11")]
f11: Option<u64>,
#[prost(uint64, optional, tag = "12")]
f12: Option<u64>,
#[prost(uint64, optional, tag = "13")]
f13: Option<u64>,
#[prost(uint64, optional, tag = "14")]
f14: Option<u64>,
#[prost(uint64, optional, tag = "15")]
f15: Option<u64>,
#[prost(uint64, optional, tag = "16")]
f16: Option<u64>,
}
fn pb_value() -> PbTelemetry {
let mut m = PbTelemetry::default();
for (id, v) in SET {
match id {
2 => m.f02 = Some(*v),
7 => m.f07 = Some(*v),
11 => m.f11 = Some(*v),
15 => m.f15 = Some(*v),
_ => unreachable!(),
}
}
m
}
#[derive(Default, Serialize, Deserialize)]
struct JTelemetry {
#[serde(skip_serializing_if = "Option::is_none")]
f02: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
f07: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
f11: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
f15: Option<u64>,
}
fn json_value() -> JTelemetry {
JTelemetry {
f02: Some(SET[0].1),
f07: Some(SET[1].1),
f11: Some(SET[2].1),
f15: Some(SET[3].1),
}
}
fn bench(name: &str, mut f: impl FnMut() -> u64) {
let mut n: u64 = 1;
let mut elapsed;
loop {
let t = Instant::now();
let mut acc = 0u64;
for _ in 0..n {
acc = acc.wrapping_add(f());
}
black_box(acc);
elapsed = t.elapsed();
if elapsed >= Duration::from_millis(150) || n >= 1 << 28 {
break;
}
n *= 2;
}
let mut best = elapsed.as_nanos() as f64 / n as f64;
for _ in 0..3 {
let t = Instant::now();
let mut acc = 0u64;
for _ in 0..n {
acc = acc.wrapping_add(f());
}
black_box(acc);
best = best.min(t.elapsed().as_nanos() as f64 / n as f64);
}
println!(" {name:<38} {best:>10.1} ns/op");
}
fn main() {
let packed = veritate_schema(true);
let sparse = veritate_schema(false);
let vp = encode(&packed, &veritate_value(), SchemaMode::HashOnly).unwrap();
let vs = encode(&sparse, &veritate_value(), SchemaMode::HashOnly).unwrap();
let pb = pb_value().encode_to_vec();
let js = serde_json::to_vec(&json_value()).unwrap();
println!(
"sparse wide record: {} of {} fields set (u64 each)\n",
SET.len(),
N
);
let env = verit::encode::HEADER_LEN;
println!("--- wire size (bytes; block = payload without the 32B envelope) ---");
println!(
" veritate (packed) {:>5} block {:>4}",
vp.len(),
vp.len() - env
);
println!(
" veritate (sparse) {:>5} block {:>4}",
vs.len(),
vs.len() - env
);
println!(" protobuf (prost) {:>5}", pb.len());
println!(" json {:>5}", js.len());
println!(
"\npacked block is {}% smaller than sparse ({} -> {} bytes) — the win the",
(100 * ((vs.len() - env) - (vp.len() - env))) / (vs.len() - env),
vs.len() - env,
vp.len() - env,
);
println!(
"feature exists for. Note the packed block ({}B) even undercuts protobuf's\n\
varints ({}B) here: fixed-width slots beat varints once values are large\n\
(ids, timestamps, hashes). Protobuf's total edge is entirely Veritate's\n\
fixed {}B envelope — the price of a self-describing, evolvable message\n\
that also keeps O(1) zero-copy random access (protobuf must scan-decode).",
vp.len() - env,
pb.len(),
env,
);
let resolver = Resolver::identity(&packed).unwrap();
println!("\n--- random access: bytes -> read fields 11 and 15 ---");
bench("veritate packed (popcount, O(1))", || {
let msg = Message::parse(&vp).unwrap();
let root = msg.root(&resolver).unwrap();
root.get_u64(11).unwrap().unwrap() + root.get_u64(15).unwrap().unwrap()
});
bench("protobuf (must decode whole msg)", || {
let m = PbTelemetry::decode(&pb[..]).unwrap();
m.f11.unwrap() + m.f15.unwrap()
});
bench("json (must decode whole msg)", || {
let m: JTelemetry = serde_json::from_slice(&js).unwrap();
m.f11.unwrap() + m.f15.unwrap()
});
println!(
"(on this tiny record protobuf's full decode is nearly free; packed's O(1)\n\
access pulls ahead as records grow — see the benchmarks doc for the large case.)"
);
let msg = Message::parse(&vp).unwrap();
let root = msg.root(&resolver).unwrap();
let vsum: u64 = SET
.iter()
.map(|(id, _)| root.get_u64(*id).unwrap().unwrap())
.sum();
let expected: u64 = SET.iter().map(|(_, v)| v).sum();
assert_eq!(vsum, expected);
println!("\nall present fields read back correctly (sum {expected})");
}