crityp/
lib.rs

1//! Crityp is a crate for benchmarking typst code.
2//!
3//! ## Usage
4//!
5//! ```rs
6//! let mut crit = criterion::Criterion::default();
7//! let mut verse = tinymist_world::CompileOnceArgs::parse().resolve()?;
8//! let mut world = verse.snapshot();
9//! crityp::bench(&mut crit, &mut world)?;
10//! crit.final_summary();
11//! ```
12
13use anyhow::Context as ContextTrait;
14use comemo::Track;
15use criterion::Criterion;
16use ecow::{EcoString, eco_format};
17use tinymist_project::LspWorld;
18use tinymist_std::path::unix_slash;
19use tinymist_std::typst_shim::eval::eval_compat;
20use typst::World;
21use typst::engine::{Engine, Route, Sink, Traced};
22use typst::foundations::{Context, Func, Value};
23use typst::introspection::Introspector;
24
25/// Runs benchmarks on the given world. An entry point must be provided in the
26/// world.
27pub fn bench(c: &mut Criterion, world: &mut LspWorld) -> anyhow::Result<()> {
28    // Gets the main source file and its path.
29    let main_source = world.source(world.main())?;
30    let main_path = unix_slash(world.main().vpath().as_rooted_path());
31
32    let traced = Traced::default();
33    let introspector = Introspector::default();
34
35    // Evaluates the main source file.
36    let module = eval_compat(world, &main_source);
37    let module = module
38        .map_err(|e| anyhow::anyhow!("{e:?}"))
39        .context("evaluation error")?;
40
41    // Collects all benchmarks.
42    let mut goals: Vec<(EcoString, &Func)> = vec![];
43    for (name, bind) in module.scope().iter() {
44        if !name.starts_with("bench") {
45            continue;
46        }
47
48        if let Value::Func(func) = bind.read() {
49            goals.push((eco_format!("{main_path}@{name}"), func));
50        }
51    }
52
53    // Runs benchmarks.
54    for (name, func) in goals {
55        let route = Route::default();
56        let mut sink = Sink::default();
57        let engine = &mut Engine {
58            routines: &typst::ROUTINES,
59            world: ((world) as &dyn World).track(),
60            introspector: introspector.track(),
61            traced: traced.track(),
62            sink: sink.track_mut(),
63            route,
64        };
65
66        // Runs the benchmark once.
67        let mut call_once = move || {
68            let context = Context::default();
69            let values = Vec::<Value>::default();
70            func.call(engine, context.track(), values)
71        };
72
73        // Calls the benchmark once to ensure it is correct.
74        // Since all typst functions are pure, we can safely ignore the result
75        // in the benchmark loop then.
76        if let Err(err) = call_once() {
77            eprintln!("call error in {name}: {err:?}");
78            continue;
79        }
80
81        // Benchmarks the function
82        c.bench_function(&name, move |b| {
83            b.iter(|| {
84                comemo::evict(0);
85                let _result = call_once();
86            })
87        });
88    }
89
90    Ok(())
91}