1use std::alloc::{GlobalAlloc, Layout, System};
53use std::sync::atomic::{AtomicU64, Ordering};
54use std::sync::Arc;
55use std::time::{Duration, Instant};
56
57use cove_rules::{
58 embedding, embedding_without_trace, package_root, Decision, PullRequest, Reviews, RulePackage,
59 Session, REVIEWS,
60};
61use cove_runtime::Limits;
62
63static ALLOCATIONS: AtomicU64 = AtomicU64::new(0);
67static BYTES: AtomicU64 = AtomicU64::new(0);
69
70struct Counting;
77
78unsafe impl GlobalAlloc for Counting {
79 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
80 ALLOCATIONS.fetch_add(1, Ordering::Relaxed);
81 BYTES.fetch_add(layout.size() as u64, Ordering::Relaxed);
82 System.alloc(layout)
83 }
84
85 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
86 System.dealloc(ptr, layout)
87 }
88
89 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
90 ALLOCATIONS.fetch_add(1, Ordering::Relaxed);
91 BYTES.fetch_add(new_size as u64, Ordering::Relaxed);
92 System.realloc(ptr, layout, new_size)
93 }
94}
95
96#[global_allocator]
97static COUNTING: Counting = Counting;
98
99#[derive(Clone, Copy, Default)]
101struct Cost {
102 elapsed: Duration,
103 allocations: u64,
104 bytes: u64,
105}
106
107fn cost<T>(work: impl FnOnce() -> T) -> (T, Cost) {
109 let allocations = ALLOCATIONS.load(Ordering::Relaxed);
110 let bytes = BYTES.load(Ordering::Relaxed);
111 let started = Instant::now();
112 let answer = work();
113 let elapsed = started.elapsed();
114 (
115 answer,
116 Cost {
117 elapsed,
118 allocations: ALLOCATIONS.load(Ordering::Relaxed) - allocations,
119 bytes: BYTES.load(Ordering::Relaxed) - bytes,
120 },
121 )
122}
123
124struct Row {
127 what: &'static str,
128 turns: u64,
129 cost: Cost,
130 instructions: Option<u64>,
131}
132
133impl Row {
134 fn print(&self) {
135 let per = self.cost.elapsed.as_nanos() as f64 / self.turns as f64;
136 let allocations = self.cost.allocations as f64 / self.turns as f64;
137 let bytes = self.cost.bytes as f64 / self.turns as f64;
138 let instructions = match self.instructions {
139 Some(count) => format!("{:>12.1}", count as f64 / self.turns as f64),
140 None => format!("{:>12}", "-"),
141 };
142 println!(
143 "{:<34} {:>12.1} {:>12.2} {:>12.1} {instructions}",
144 self.what, per, allocations, bytes
145 );
146 }
147}
148
149#[derive(Clone, Copy)]
157enum Way {
158 Command(&'static str),
161 Direct,
163}
164
165impl Way {
166 fn call(self, session: &mut Session<'_>, module: &str, entry: &str, pr: &PullRequest) {
167 match self {
168 Way::Command(argument) => {
169 session
170 .run(module, entry, &[argument])
171 .expect("every invocation succeeds");
172 }
173 Way::Direct => {
174 session
175 .invoke(module, entry, vec![pr.to_policy()])
176 .expect("every invocation succeeds");
177 }
178 }
179 }
180}
181
182fn header(title: &str) {
184 println!();
185 println!("{title}");
186 println!(
187 "{:<34} {:>12} {:>12} {:>12} {:>12}",
188 "", "ns/turn", "allocs/turn", "bytes/turn", "insts/turn"
189 );
190}
191
192fn main() {
195 let turns: u64 = std::env::args()
196 .nth(1)
197 .and_then(|arg| arg.parse().ok())
198 .unwrap_or(1000);
199
200 cove_runtime::on_cove_stack(move || study(turns)).expect("a thread to run Cove on");
201}
202
203fn study(turns: u64) {
205 let (package, load) =
207 cost(|| RulePackage::load(&package_root(), REVIEWS).expect("the rule package checks"));
208 let detail = package.cost();
209
210 header(&format!(
211 "paid once, over {} file(s) in {} module(s)",
212 detail.files, detail.modules
213 ));
214 Row {
215 what: "load: read, parse, and check",
216 turns: 1,
217 cost: load,
218 instructions: None,
219 }
220 .print();
221 println!(
222 "{:<34} {:>12.1} {:>12} {:>12} {:>12}",
223 " of which: read from disk",
224 detail.read.as_nanos() as f64,
225 "-",
226 "-",
227 "-"
228 );
229 println!(
230 "{:<34} {:>12.1} {:>12} {:>12} {:>12}",
231 " of which: parse",
232 detail.parse.as_nanos() as f64,
233 "-",
234 "-",
235 "-"
236 );
237 println!(
238 "{:<34} {:>12.1} {:>12} {:>12} {:>12}",
239 " of which: resolve and check",
240 detail.check.as_nanos() as f64,
241 "-",
242 "-",
243 "-"
244 );
245
246 const LOWERINGS: u64 = 20;
251 for (module, entry) in [
252 ("rules", "floor"),
253 ("rules", "decideSample"),
254 ("rules.embedded", "evaluate"),
255 ("rules.embedded", "pullOnly"),
256 ("rules.embedded", "decideRequest"),
257 ] {
258 let lowering = package
259 .lower(module, entry)
260 .unwrap_or_else(|why| panic!("{module}.{entry} lowers: {why}"));
261 let (_, lowered) = cost(|| {
262 for _ in 0..LOWERINGS {
263 package.lower(module, entry).expect("the entry lowers");
264 }
265 });
266 println!(
267 "{:<34} {:>12.1} {:>12.2} {:>12.1} {:>12}",
268 format!("lower {module}.{entry} ({} fns)", lowering.functions),
269 lowered.elapsed.as_nanos() as f64 / LOWERINGS as f64,
270 lowered.allocations as f64 / LOWERINGS as f64,
271 lowered.bytes as f64 / LOWERINGS as f64,
272 "-"
273 );
274 }
275
276 let subject: PullRequest = cove_rules::samples()
278 .remove("req-2")
279 .expect("the sample exists");
280 header("paid per invocation, one Vm serving all of them");
281 for (what, module, entry, way, grants, trace) in [
282 (
283 "floor: an entry that does nothing",
284 "rules",
285 "floor",
286 Way::Command("0"),
287 &[][..],
288 false,
289 ),
290 (
291 "decide, no host call",
292 "rules",
293 "decideSample",
294 Way::Command("1"),
295 &[][..],
296 false,
297 ),
298 (
299 "evaluate: the request as argument",
300 "rules.embedded",
301 "evaluate",
302 Way::Direct,
303 &[][..],
304 false,
305 ),
306 (
307 "pull only: one host call",
308 "rules.embedded",
309 "pullOnly",
310 Way::Command("req-2"),
311 &["reviews"][..],
312 false,
313 ),
314 (
315 "decide, two host calls",
316 "rules.embedded",
317 "decideRequest",
318 Way::Command("req-2"),
319 &["reviews"][..],
320 false,
321 ),
322 (
323 "evaluate, traced",
324 "rules.embedded",
325 "evaluate",
326 Way::Direct,
327 &[][..],
328 true,
329 ),
330 (
331 "pull only, traced",
332 "rules.embedded",
333 "pullOnly",
334 Way::Command("req-2"),
335 &["reviews"][..],
336 true,
337 ),
338 (
339 "decide, two host calls, traced",
340 "rules.embedded",
341 "decideRequest",
342 Way::Command("req-2"),
343 &["reviews"][..],
344 true,
345 ),
346 ] {
347 let lowering = package.lower(module, entry).expect("the entry lowers");
348 let reviews = Reviews::new(cove_rules::samples());
349 let embed = if trace {
350 embedding(reviews, grants, Limits::default())
351 } else {
352 embedding_without_trace(reviews, grants, Limits::default())
353 };
354 let (instructions, measured) = package.serve(
355 Arc::clone(&embed.hosts),
356 Some(&lowering),
357 |session: &mut Session<'_>| {
358 way.call(session, module, entry, &subject);
361 let before = session.instructions().unwrap_or_default();
362 let (_, measured) = cost(|| {
363 for _ in 0..turns {
364 way.call(session, module, entry, &subject);
365 }
366 });
367 (
368 session.instructions().unwrap_or_default() - before,
369 measured,
370 )
371 },
372 );
373 Row {
374 what,
375 turns,
376 cost: measured,
377 instructions: Some(instructions),
378 }
379 .print();
380 }
381
382 header("the same decision, with the session rebuilt each time");
384 let lowering = package
385 .lower("rules.embedded", "decideRequest")
386 .expect("the entry lowers");
387 let embed = embedding_without_trace(
388 Reviews::new(cove_rules::samples()),
389 &["reviews"],
390 Limits::default(),
391 );
392 let (_, rebuilt) = cost(|| {
393 for _ in 0..turns {
394 package.serve(Arc::clone(&embed.hosts), Some(&lowering), |session| {
395 session
396 .run("rules.embedded", "decideRequest", &["req-2"])
397 .expect("every invocation succeeds");
398 });
399 }
400 });
401 Row {
402 what: "decide, a new Runtime and Vm each",
403 turns,
404 cost: rebuilt,
405 instructions: None,
406 }
407 .print();
408
409 let (_, interpreted) = cost(|| {
410 package.serve(Arc::clone(&embed.hosts), None, |session| {
411 for _ in 0..turns {
412 session
413 .run("rules.embedded", "decideRequest", &["req-2"])
414 .expect("every invocation succeeds");
415 }
416 });
417 });
418 Row {
419 what: "decide, on the interpreter",
420 turns,
421 cost: interpreted,
422 instructions: None,
423 }
424 .print();
425
426 header("the conversion, measured on the Rust side alone");
428 let (_, into_cove) = cost(|| {
429 for _ in 0..turns {
430 std::hint::black_box(subject.to_cove());
431 }
432 });
433 Row {
434 what: "PullRequest::to_cove",
435 turns,
436 cost: into_cove,
437 instructions: None,
438 }
439 .print();
440
441 let (_, into_policy) = cost(|| {
445 for _ in 0..turns {
446 std::hint::black_box(subject.to_policy());
447 }
448 });
449 Row {
450 what: "PullRequest::to_policy",
451 turns,
452 cost: into_policy,
453 instructions: None,
454 }
455 .print();
456
457 let answer = package.serve(Arc::clone(&embed.hosts), Some(&lowering), |session| {
458 session
459 .run("rules.embedded", "decideRequest", &["req-2"])
460 .expect("the invocation succeeds")
461 });
462 let (_, out_of_cove) = cost(|| {
463 for _ in 0..turns {
464 std::hint::black_box(Decision::from_cove(&answer).expect("the answer decodes"));
465 }
466 });
467 Row {
468 what: "Decision::from_cove",
469 turns,
470 cost: out_of_cove,
471 instructions: None,
472 }
473 .print();
474
475 println!();
476 println!("{turns} turn(s) a row.");
477}