Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

tangled-core / bobbin / crates / ingest / benches / json_decode.rs
7.9 kB 245 lines
1use std::hint::black_box; 2 3use bobbin_types::edges::Record; 4use criterion::{Criterion, criterion_group, criterion_main}; 5use jacquard_common::DefaultStr; 6use jacquard_common::types::nsid::Nsid; 7use serde::Deserialize; 8use serde_json::value::RawValue; 9 10#[derive(Deserialize)] 11struct ValueFrame { 12 record: ValueRecordFrame, 13} 14 15#[derive(Deserialize)] 16struct ValueRecordFrame { 17 collection: Nsid<DefaultStr>, 18 #[serde(default)] 19 record: Option<serde_json::Value>, 20} 21 22#[derive(Deserialize)] 23struct BorrowedRawFrame<'a> { 24 #[serde(borrow)] 25 record: BorrowedRawRecordFrame<'a>, 26} 27 28#[derive(Deserialize)] 29struct BorrowedRawRecordFrame<'a> { 30 collection: Nsid<DefaultStr>, 31 #[serde(borrow, default)] 32 record: Option<&'a RawValue>, 33} 34 35#[derive(Deserialize)] 36struct OwnedRawFrame { 37 record: OwnedRawRecordFrame, 38} 39 40#[derive(Deserialize)] 41struct OwnedRawRecordFrame { 42 collection: Nsid<DefaultStr>, 43 #[serde(default)] 44 record: Option<Box<RawValue>>, 45} 46 47fn current_path(text: &str) -> Record { 48 let frame: ValueFrame = serde_json::from_str(text).expect("frame"); 49 let value = frame.record.record.expect("upsert body"); 50 let bytes = serde_json::to_vec(&value).expect("re-serialize Value"); 51 Record::from_json_bytes(&frame.record.collection, &bytes).expect("decode record") 52} 53 54fn raw_value_borrowed(text: &str) -> Record { 55 let frame: BorrowedRawFrame<'_> = serde_json::from_str(text).expect("frame"); 56 let raw = frame.record.record.expect("upsert body"); 57 Record::from_json_bytes(&frame.record.collection, raw.get().as_bytes()).expect("decode record") 58} 59 60fn raw_value_owned(text: &str) -> Record { 61 let frame: OwnedRawFrame = serde_json::from_str(text).expect("frame"); 62 let raw = frame.record.record.expect("upsert body"); 63 Record::from_json_bytes(&frame.record.collection, raw.get().as_bytes()).expect("decode record") 64} 65 66fn floor_record_only(collection: &Nsid<DefaultStr>, record_bytes: &[u8]) -> Record { 67 Record::from_json_bytes(collection, record_bytes).expect("decode record") 68} 69 70fn star_text() -> &'static str { 71 r#"{ 72 "id": 1, 73 "type": "record", 74 "record": { 75 "live": false, 76 "did": "did:plc:olaren", 77 "rev": "3lq2zk5wqsh2k", 78 "collection": "sh.tangled.feed.star", 79 "rkey": "abcabcabcabcz", 80 "action": "create", 81 "record": { 82 "$type": "sh.tangled.feed.star", 83 "createdAt": "2026-05-01T00:00:00Z", 84 "subject": "at://did:plc:abalone/sh.tangled.repo/3lq2zk5wq0000" 85 } 86 } 87 }"# 88} 89 90fn repo_text() -> &'static str { 91 r#"{ 92 "id": 2, 93 "type": "record", 94 "record": { 95 "live": false, 96 "did": "did:plc:nel", 97 "rev": "3lq2zk5wqsh2l", 98 "collection": "sh.tangled.repo", 99 "rkey": "3lq2zk5wq0001", 100 "action": "create", 101 "record": { 102 "$type": "sh.tangled.repo", 103 "createdAt": "2026-05-01T00:00:00Z", 104 "knot": "knot.witchcraft.systems", 105 "name": "limpet", 106 "description": "demonstration repository for the bench corpus", 107 "owner": "did:plc:nel", 108 "repoDid": "did:plc:limpet", 109 "labels": [ 110 "at://did:plc:periwinkle/sh.tangled.label.definition/3lq2zk5wq0010", 111 "at://did:plc:periwinkle/sh.tangled.label.definition/3lq2zk5wq0011" 112 ] 113 } 114 } 115 }"# 116} 117 118fn issue_text() -> &'static str { 119 r#"{ 120 "id": 3, 121 "type": "record", 122 "record": { 123 "live": false, 124 "did": "did:plc:teq", 125 "rev": "3lq2zk5wqsh2m", 126 "collection": "sh.tangled.repo.issue", 127 "rkey": "3lq2zk5wq0100", 128 "action": "create", 129 "record": { 130 "$type": "sh.tangled.repo.issue", 131 "createdAt": "2026-05-01T00:00:00Z", 132 "title": "ingest: single-pass JSON decode follow-up corpus entry", 133 "body": "Long body to give the bench a realistic decode cost. Repeats: blahhhhh meow meow aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 134 "repo": "at://did:plc:limpet", 135 "repoDid": "did:plc:limpet", 136 "mentions": [ 137 "did:plc:nel", 138 "did:plc:olaren", 139 "did:plc:bailey" 140 ], 141 "references": [ 142 "at://did:plc:limpet/sh.tangled.repo.issue/3lq2zk5wq0099", 143 "at://did:plc:limpet/sh.tangled.repo.pull/3lq2zk5wq0098" 144 ] 145 } 146 } 147 }"# 148} 149 150fn pull_comment_text() -> &'static str { 151 r#"{ 152 "id": 4, 153 "type": "record", 154 "record": { 155 "live": false, 156 "did": "did:plc:lyna", 157 "rev": "3lq2zk5wqsh2n", 158 "collection": "sh.tangled.repo.pull.comment", 159 "rkey": "3lq2zk5wq0200", 160 "action": "create", 161 "record": { 162 "$type": "sh.tangled.repo.pull.comment", 163 "createdAt": "2026-05-01T00:00:00Z", 164 "body": "lgtm i thinks!!!! but please verify the cursor invariant under buffered(N) before landing. :3", 165 "pull": "at://did:plc:limpet/sh.tangled.repo.pull/3lq2zk5wq0098", 166 "owner": "did:plc:lyna" 167 } 168 } 169 }"# 170} 171 172fn follow_text() -> &'static str { 173 r#"{ 174 "id": 5, 175 "type": "record", 176 "record": { 177 "live": false, 178 "did": "did:plc:bailey", 179 "rev": "3lq2zk5wqsh2o", 180 "collection": "sh.tangled.graph.follow", 181 "rkey": "3lq2zk5wq0300", 182 "action": "create", 183 "record": { 184 "$type": "sh.tangled.graph.follow", 185 "createdAt": "2026-05-01T00:00:00Z", 186 "subject": "did:plc:nel" 187 } 188 } 189 }"# 190} 191 192fn extract_record_slice(text: &str) -> (Nsid<DefaultStr>, Vec<u8>) { 193 let frame: ValueFrame = serde_json::from_str(text).expect("frame"); 194 let value = frame.record.record.expect("upsert body"); 195 let bytes = serde_json::to_vec(&value).expect("re-serialize Value"); 196 (frame.record.collection, bytes) 197} 198 199fn bench_decode(c: &mut Criterion) { 200 let corpus: &[(&str, &str)] = &[ 201 ("star", star_text()), 202 ("repo", repo_text()), 203 ("issue", issue_text()), 204 ("pull_comment", pull_comment_text()), 205 ("follow", follow_text()), 206 ]; 207 for (name, text) in corpus { 208 let mut group = c.benchmark_group(format!("decode/{name}")); 209 let (collection, record_bytes) = extract_record_slice(text); 210 211 group.bench_function("current_path", |b| { 212 b.iter(|| { 213 let r = current_path(black_box(text)); 214 black_box(r); 215 }); 216 }); 217 218 group.bench_function("raw_value_borrowed", |b| { 219 b.iter(|| { 220 let r = raw_value_borrowed(black_box(text)); 221 black_box(r); 222 }); 223 }); 224 225 group.bench_function("raw_value_owned", |b| { 226 b.iter(|| { 227 let r = raw_value_owned(black_box(text)); 228 black_box(r); 229 }); 230 }); 231 232 group.bench_function("floor_record_only", |b| { 233 let bytes = record_bytes.as_slice(); 234 b.iter(|| { 235 let r = floor_record_only(black_box(&collection), black_box(bytes)); 236 black_box(r); 237 }); 238 }); 239 240 group.finish(); 241 } 242} 243 244criterion_group!(benches, bench_decode); 245criterion_main!(benches);