From c44ecbfbd2706bf8e09728f2c85135aa2299d542 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Aug 2021 18:12:26 +0200 Subject: [PATCH] Move to exclusively oneshot benchmarks with Iai fork (#41) --- Cargo.toml | 9 ++- bench/Cargo.toml | 25 --------- bench/src/clock.rs | 129 ------------------------------------------- bench/src/parsing.rs | 38 ------------- benches/bench.typ | 45 +++++++++++++++ benches/oneshot.rs | 82 +++++++++++++++++++++++++++ src/lib.rs | 28 ++++++++-- 7 files changed, 157 insertions(+), 199 deletions(-) delete mode 100644 bench/Cargo.toml delete mode 100644 bench/src/clock.rs delete mode 100644 bench/src/parsing.rs create mode 100644 benches/bench.typ create mode 100644 benches/oneshot.rs diff --git a/Cargo.toml b/Cargo.toml index 5f7f28f6f..616ad9f93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,6 @@ cli = ["anyhow", "codespan-reporting", "fs", "same-file"] fs = ["dirs", "memmap2", "same-file", "walkdir"] layout-cache = [] -[workspace] -members = ["bench"] - [profile.dev] # Faster compilation debug = 0 @@ -44,6 +41,7 @@ walkdir = { version = "2", optional = true } walkdir = "2" tiny-skia = "0.5" usvg = { version = "0.15", default-features = false } +iai = { git = "https://github.com/reknih/iai" } [[bin]] name = "typst" @@ -53,3 +51,8 @@ required-features = ["cli"] name = "typeset" required-features = ["fs"] harness = false + +[[bench]] +name = "oneshot" +path = "benches/oneshot.rs" +harness = false diff --git a/bench/Cargo.toml b/bench/Cargo.toml deleted file mode 100644 index eec8a96eb..000000000 --- a/bench/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "typst-bench" -version = "0.0.1" -authors = ["The Typst Project Developers"] -edition = "2018" -publish = false - -[features] -default = ["layout-cache"] -layout-cache = ["typst/layout-cache"] - -[dev-dependencies] -criterion = { version = "0.3", features = ["html_reports"] } -iai = "0.1" -typst = { path = "..", default-features = false, features = ["fs"] } - -[[bench]] -name = "clock" -path = "src/clock.rs" -harness = false - -[[bench]] -name = "parsing" -path = "src/parsing.rs" -harness = false diff --git a/bench/src/clock.rs b/bench/src/clock.rs deleted file mode 100644 index 22f1b6dfb..000000000 --- a/bench/src/clock.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::cell::RefCell; -use std::path::Path; -use std::rc::Rc; - -use criterion::{criterion_group, criterion_main, Criterion}; - -use typst::diag::TypResult; -use typst::eval::{eval, Module, State}; -use typst::export::pdf; -use typst::layout::{layout, Frame, LayoutTree}; -use typst::loading::FsLoader; -use typst::parse::parse; -use typst::source::SourceId; -use typst::syntax::SyntaxTree; -use typst::Context; - -const FONT_DIR: &str = "../fonts"; -const TYP_DIR: &str = "../tests/typ"; -const CASES: &[&str] = &["coma.typ", "text/basic.typ"]; - -fn benchmarks(c: &mut Criterion) { - let loader = FsLoader::new().with_path(FONT_DIR).wrap(); - let ctx = Rc::new(RefCell::new(Context::new(loader))); - - for case in CASES { - let path = Path::new(TYP_DIR).join(case); - let name = path.file_stem().unwrap().to_string_lossy(); - let id = ctx.borrow_mut().sources.load(&path).unwrap(); - let case = Case::new(ctx.clone(), id); - - macro_rules! bench { - ($step:literal, setup: |$ctx:ident| $setup:expr, code: $code:expr $(,)?) => { - c.bench_function(&format!("{}-{}", $step, name), |b| { - b.iter_batched( - || { - let mut $ctx = ctx.borrow_mut(); - $setup - }, - |_| $code, - criterion::BatchSize::PerIteration, - ) - }); - }; - ($step:literal, $code:expr) => { - c.bench_function(&format!("{}-{}", $step, name), |b| b.iter(|| $code)); - }; - } - - bench!("parse", case.parse()); - bench!("eval", case.eval()); - bench!("build", case.build()); - - #[cfg(not(feature = "layout-cache"))] - { - bench!("layout", case.layout()); - bench!("typeset", case.typeset()); - } - - #[cfg(feature = "layout-cache")] - { - bench!("layout", setup: |ctx| ctx.layouts.clear(), code: case.layout()); - bench!("typeset", setup: |ctx| ctx.layouts.clear(), code: case.typeset()); - bench!("layout-cached", case.layout()); - bench!("typeset-cached", case.typeset()); - } - - bench!("pdf", case.pdf()); - } -} - -/// A test case with prepared intermediate results. -struct Case { - ctx: Rc>, - state: State, - id: SourceId, - ast: SyntaxTree, - module: Module, - tree: LayoutTree, - frames: Vec>, -} - -impl Case { - fn new(ctx: Rc>, id: SourceId) -> Self { - let mut borrowed = ctx.borrow_mut(); - let state = State::default(); - let source = borrowed.sources.get(id); - let ast = parse(source).unwrap(); - let module = eval(&mut borrowed, id, &ast).unwrap(); - let tree = module.template.to_tree(&state); - let frames = layout(&mut borrowed, &tree); - drop(borrowed); - Self { - ctx, - state, - id, - ast, - module, - tree, - frames, - } - } - - fn parse(&self) -> SyntaxTree { - parse(self.ctx.borrow().sources.get(self.id)).unwrap() - } - - fn eval(&self) -> TypResult { - eval(&mut self.ctx.borrow_mut(), self.id, &self.ast) - } - - fn build(&self) -> LayoutTree { - self.module.template.to_tree(&self.state) - } - - fn layout(&self) -> Vec> { - layout(&mut self.ctx.borrow_mut(), &self.tree) - } - - fn typeset(&self) -> TypResult>> { - self.ctx.borrow_mut().typeset(self.id) - } - - fn pdf(&self) -> Vec { - pdf(&self.ctx.borrow(), &self.frames) - } -} - -criterion_group!(benches, benchmarks); -criterion_main!(benches); diff --git a/bench/src/parsing.rs b/bench/src/parsing.rs deleted file mode 100644 index f95dfe75a..000000000 --- a/bench/src/parsing.rs +++ /dev/null @@ -1,38 +0,0 @@ -use iai::{black_box, main}; - -use typst::diag::TypResult; -use typst::parse::{parse, Scanner, TokenMode, Tokens}; -use typst::source::SourceFile; -use typst::syntax::SyntaxTree; - -const SRC: &str = include_str!("../../tests/typ/coma.typ"); - -fn bench_decode() -> usize { - // We don't use chars().count() because that has a special - // superfast implementation. - let mut count = 0; - let mut chars = black_box(SRC).chars(); - while let Some(_) = chars.next() { - count += 1; - } - count -} - -fn bench_scan() -> usize { - let mut count = 0; - let mut scanner = Scanner::new(black_box(SRC)); - while let Some(_) = scanner.eat() { - count += 1; - } - count -} - -fn bench_tokenize() -> usize { - Tokens::new(black_box(SRC), black_box(TokenMode::Markup)).count() -} - -fn bench_parse() -> TypResult { - parse(&SourceFile::detached(black_box(SRC))) -} - -main!(bench_decode, bench_scan, bench_tokenize, bench_parse); diff --git a/benches/bench.typ b/benches/bench.typ new file mode 100644 index 000000000..f290844bf --- /dev/null +++ b/benches/bench.typ @@ -0,0 +1,45 @@ +// Configuration with `page` and `font` functions. +#page(width: 450pt, margins: 1cm) + +// There are variables and they can take normal values like strings, ... +#let city = "Berlin" + +// ... but also "template" values. While these contain markup, +// they are also values and can be summed, stored in arrays etc. +// There are also more standard control flow structures, like #if and #for. +#let university = [*Technische Universität {city}*] +#let faculty = [*Fakultät II, Institut for Mathematik*] + +// The `box` function just places content into a rectangular container. When +// the only argument to a function is a template, the parentheses can be omitted +// (i.e. `f[a]` is the same as `f([a])`). +#box[ + // Backslash adds a forced line break. + #university \ + #faculty \ + Sekretariat MA \ + Dr. Max Mustermann \ + Ola Nordmann, John Doe +] +#align(right, box[*WiSe 2019/2020* \ Woche 3]) + +// Adds vertical spacing. +#v(6mm) + +// If the last argument to a function is a template, we can also place it behind +// the parentheses. +#align(center)[ + // Markdown-like syntax for headings. + ==== 3. Übungsblatt Computerorientierte Mathematik II #v(4mm) + *Abgabe: 03.05.2019* (bis 10:10 Uhr in MA 001) #v(4mm) + *Alle Antworten sind zu beweisen.* +] + +*1. Aufgabe* #align(right)[(1 + 1 + 2 Punkte)] + +Ein _Binärbaum_ ist ein Wurzelbaum, in dem jeder Knoten ≤ 2 Kinder hat. +Die Tiefe eines Knotens _v_ ist die Länge des eindeutigen Weges von der Wurzel +zu _v_, und die Höhe von _v_ ist die Länge eines längsten (absteigenden) Weges +von _v_ zu einem Blatt. Die Höhe des Baumes ist die Höhe der Wurzel. + +#v(6mm) diff --git a/benches/oneshot.rs b/benches/oneshot.rs new file mode 100644 index 000000000..5f11589b2 --- /dev/null +++ b/benches/oneshot.rs @@ -0,0 +1,82 @@ +use std::path::Path; + +use iai::{black_box, main, Iai}; + +use typst::eval::eval; +use typst::layout::layout; +use typst::loading::{MemLoader}; +use typst::parse::{parse, Scanner, TokenMode, Tokens}; +use typst::source::{SourceFile, SourceId}; +use typst::Context; + +const SRC: &str = include_str!("bench.typ"); + +fn context() -> (Context, SourceId) { + let font = include_bytes!("../fonts/EBGaramond-Regular.ttf"); + let loader = MemLoader::new() + .with(Path::new("EBGaramond-Regular.ttf"), &font[..]) + .wrap(); + let mut ctx = Context::new(loader); + let id = ctx.sources.provide(Path::new(""), SRC.to_string()); + (ctx, id) +} + +fn bench_decode(iai: &mut Iai) { + iai.run(|| { + // We don't use chars().count() because that has a special + // superfast implementation. + let mut count = 0; + let mut chars = black_box(SRC).chars(); + while let Some(_) = chars.next() { + count += 1; + } + count + }) +} + +fn bench_scan(iai: &mut Iai) { + iai.run(|| { + let mut count = 0; + let mut scanner = Scanner::new(black_box(SRC)); + while let Some(_) = scanner.eat() { + count += 1; + } + count + }) +} + +fn bench_tokenize(iai: &mut Iai) { + iai.run(|| Tokens::new(black_box(SRC), black_box(TokenMode::Markup)).count()); +} + +fn bench_parse(iai: &mut Iai) { + iai.run(|| parse(&SourceFile::detached(SRC))); +} + +fn bench_eval(iai: &mut Iai) { + let (mut ctx, id) = context(); + let ast = ctx.parse(id).unwrap(); + iai.run(|| eval(&mut ctx, id, &ast).unwrap()); +} + +fn bench_to_tree(iai: &mut Iai) { + let (mut ctx, id) = context(); + let module = ctx.evaluate(id).unwrap(); + iai.run(|| module.template.to_tree(ctx.state())); +} + +fn bench_layout(iai: &mut Iai) { + let (mut ctx, id) = context(); + let tree = ctx.execute(id).unwrap(); + iai.run(|| layout(&mut ctx, &tree)); +} + +main!( + bench_decode, + bench_scan, + bench_tokenize, + bench_parse, + bench_eval, + bench_to_tree, + bench_layout +); diff --git a/src/lib.rs b/src/lib.rs index b6484012e..207567f83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,8 @@ pub mod util; use std::rc::Rc; use crate::diag::TypResult; -use crate::eval::{Scope, State}; +use crate::eval::{Scope, State, Module}; +use crate::syntax::SyntaxTree; use crate::font::FontStore; use crate::image::ImageStore; #[cfg(feature = "layout-cache")] @@ -88,11 +89,30 @@ impl Context { ContextBuilder::default() } + /// A read-only reference to the standard library scope. + pub fn std(&self) -> &Scope { + &self.std + } + + /// A read-only reference to the state. + pub fn state(&self) -> &State { + &self.state + } + + /// Parse a source file and return the resulting syntax tree. + pub fn parse(&mut self, id: SourceId) -> TypResult { + parse::parse(self.sources.get(id)) + } + + /// Evaluate a source file and return the resulting module. + pub fn evaluate(&mut self, id: SourceId) -> TypResult { + let ast = self.parse(id)?; + eval::eval(self, id, &ast) + } + /// Execute a source file and produce the resulting layout tree. pub fn execute(&mut self, id: SourceId) -> TypResult { - let source = self.sources.get(id); - let ast = parse::parse(source)?; - let module = eval::eval(self, id, &ast)?; + let module = self.evaluate(id)?; Ok(module.template.to_tree(&self.state)) }