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 tinymist_std::typst_shim::syntax::VirtualPathExt;
21use typst::World;
22use typst::engine::{Engine, Route, Sink, Traced};
23use typst::foundations::{Context, Func, Value};
24use typst::introspection::EmptyIntrospector;
25
26/// Runs benchmarks on the given world. An entry point must be provided in the
27/// world.
28pub fn bench(c: &mut Criterion, world: &mut LspWorld) -> anyhow::Result<()> {
29    // Gets the main source file and its path.
30    let main_source = world.source(world.main())?;
31    let main_path = unix_slash(world.main().vpath().as_rooted_path_compat());
32
33    let library = world.library();
34    let traced = Traced::default();
35    let introspector = EmptyIntrospector;
36
37    // Evaluates the main source file.
38    let module = eval_compat(world, &main_source);
39    let module = module
40        .map_err(|e| anyhow::anyhow!("{e:?}"))
41        .context("evaluation error")?;
42
43    // Collects all benchmarks.
44    let mut goals: Vec<(EcoString, &Func)> = vec![];
45    for (name, bind) in module.scope().iter() {
46        if !name.starts_with("bench") {
47            continue;
48        }
49
50        if let Value::Func(func) = bind.read() {
51            goals.push((eco_format!("{main_path}@{name}"), func));
52        }
53    }
54
55    // Runs benchmarks.
56    for (name, func) in goals {
57        let route = Route::default();
58        let mut sink = Sink::default();
59        let engine = &mut Engine {
60            library,
61            world: ((world) as &dyn World).track(),
62            introspector: typst::utils::Protected::new(introspector.track()),
63            traced: traced.track(),
64            sink: sink.track_mut(),
65            route,
66        };
67
68        // Runs the benchmark once.
69        let mut call_once = move || {
70            let context = Context::default();
71            let values = Vec::<Value>::default();
72            func.call(engine, context.track(), values)
73        };
74
75        // Calls the benchmark once to ensure it is correct.
76        // Since all typst functions are pure, we can safely ignore the result
77        // in the benchmark loop then.
78        if let Err(err) = call_once() {
79            eprintln!("call error in {name}: {err:?}");
80            continue;
81        }
82
83        // Benchmarks the function
84        c.bench_function(&name, move |b| {
85            b.iter(|| {
86                comemo::evict(0);
87                let _result = call_once();
88            })
89        });
90    }
91
92    Ok(())
93}