Add a cache for unchanged layouts

Co-Authored-By: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
Martin Haug 2021-05-26 23:36:03 +02:00 committed by Laurenz
parent e27f6c1014
commit 8e700606bb
6 changed files with 68 additions and 11 deletions

View File

@ -2,6 +2,7 @@ use std::path::Path;
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use typst::cache::Cache;
use typst::env::{Env, FsLoader}; use typst::env::{Env, FsLoader};
use typst::eval::eval; use typst::eval::eval;
use typst::exec::{exec, State}; use typst::exec::{exec, State};
@ -42,14 +43,14 @@ fn benchmarks(c: &mut Criterion) {
let syntax_tree = parse(&src).output; let syntax_tree = parse(&src).output;
let expr_map = eval(&mut env, &syntax_tree, &scope).output; let expr_map = eval(&mut env, &syntax_tree, &scope).output;
let layout_tree = exec(&mut env, &syntax_tree, &expr_map, state.clone()).output; let layout_tree = exec(&mut env, &syntax_tree, &expr_map, state.clone()).output;
let frames = layout(&mut env, &layout_tree); let frames = layout(&mut env, &mut Cache::new(), &layout_tree);
// Bench! // Bench!
bench!("parse": parse(&src)); bench!("parse": parse(&src));
bench!("eval": eval(&mut env, &syntax_tree, &scope)); bench!("eval": eval(&mut env, &syntax_tree, &scope));
bench!("exec": exec(&mut env, &syntax_tree, &expr_map, state.clone())); bench!("exec": exec(&mut env, &syntax_tree, &expr_map, state.clone()));
bench!("layout": layout(&mut env, &layout_tree)); bench!("layout": layout(&mut env, &mut Cache::new(), &layout_tree));
bench!("typeset": typeset(&mut env, &src, &scope, state.clone())); bench!("typeset": typeset(&mut env, &mut Cache::new(), &src, &scope, state.clone()));
bench!("pdf": pdf::export(&env, &frames)); bench!("pdf": pdf::export(&env, &frames));
} }
} }

34
src/cache.rs Normal file
View File

@ -0,0 +1,34 @@
//! Caching for incremental compilation.
use std::collections::HashMap;
use crate::layout::{Frame, Regions};
/// A cache for incremental compilation.
#[derive(Default, Debug, Clone)]
pub struct Cache {
/// A map that holds the layouted nodes from past compilations.
pub frames: HashMap<u64, FramesEntry>,
}
impl Cache {
/// Create a new, empty cache.
pub fn new() -> Self {
Self::default()
}
/// Clear the cache.
pub fn clear(&mut self) {
self.frames.clear();
}
}
/// Frames from past compilations and checks for their validity in future
/// compilations.
#[derive(Debug, Clone)]
pub struct FramesEntry {
/// The regions in which these frames are valid.
pub regions: Regions,
/// Cached frames for a node.
pub frames: Vec<Frame>,
}

View File

@ -23,12 +23,13 @@ use std::hash::{Hash, Hasher};
use decorum::NotNan; use decorum::NotNan;
use fxhash::FxHasher64; use fxhash::FxHasher64;
use crate::cache::{Cache, FramesEntry};
use crate::env::Env; use crate::env::Env;
use crate::geom::*; use crate::geom::*;
/// Layout a tree into a collection of frames. /// Layout a tree into a collection of frames.
pub fn layout(env: &mut Env, tree: &Tree) -> Vec<Frame> { pub fn layout(env: &mut Env, cache: &mut Cache, tree: &Tree) -> Vec<Frame> {
tree.layout(&mut LayoutContext { env }) tree.layout(&mut LayoutContext { env, cache })
} }
/// A tree of layout nodes. /// A tree of layout nodes.
@ -96,7 +97,19 @@ impl AnyNode {
impl Layout for AnyNode { impl Layout for AnyNode {
fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> { fn layout(&self, ctx: &mut LayoutContext, regions: &Regions) -> Vec<Frame> {
self.node.layout(ctx, regions) if let Some(hit) = ctx.cache.frames.get(&self.hash()) {
if &hit.regions == regions {
return hit.frames.clone();
}
}
let frames = self.node.layout(ctx, regions);
ctx.cache.frames.insert(self.hash(), FramesEntry {
regions: regions.clone(),
frames: frames.clone(),
});
frames
} }
} }
@ -164,6 +177,9 @@ pub trait Layout {
pub struct LayoutContext<'a> { pub struct LayoutContext<'a> {
/// The environment from which fonts are gathered. /// The environment from which fonts are gathered.
pub env: &'a mut Env, pub env: &'a mut Env,
/// A cache which enables reuse of layout artifacts from past compilation
/// cycles.
pub cache: &'a mut Cache,
} }
/// A sequence of regions to layout into. /// A sequence of regions to layout into.

View File

@ -32,6 +32,7 @@
pub mod diag; pub mod diag;
#[macro_use] #[macro_use]
pub mod eval; pub mod eval;
pub mod cache;
pub mod color; pub mod color;
pub mod env; pub mod env;
pub mod exec; pub mod exec;
@ -46,6 +47,7 @@ pub mod pretty;
pub mod syntax; pub mod syntax;
pub mod util; pub mod util;
use crate::cache::Cache;
use crate::diag::Pass; use crate::diag::Pass;
use crate::env::Env; use crate::env::Env;
use crate::eval::Scope; use crate::eval::Scope;
@ -55,6 +57,7 @@ use crate::layout::Frame;
/// Process source code directly into a collection of frames. /// Process source code directly into a collection of frames.
pub fn typeset( pub fn typeset(
env: &mut Env, env: &mut Env,
cache: &mut Cache,
src: &str, src: &str,
scope: &Scope, scope: &Scope,
state: State, state: State,
@ -62,7 +65,7 @@ pub fn typeset(
let parsed = parse::parse(src); let parsed = parse::parse(src);
let evaluated = eval::eval(env, &parsed.output, scope); let evaluated = eval::eval(env, &parsed.output, scope);
let executed = exec::exec(env, &parsed.output, &evaluated.output, state); let executed = exec::exec(env, &parsed.output, &evaluated.output, state);
let frames = layout::layout(env, &executed.output); let frames = layout::layout(env, cache, &executed.output);
let mut diags = parsed.diags; let mut diags = parsed.diags;
diags.extend(evaluated.diags); diags.extend(evaluated.diags);

View File

@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
use anyhow::{anyhow, bail, Context}; use anyhow::{anyhow, bail, Context};
use typst::cache::Cache;
use typst::diag::Pass; use typst::diag::Pass;
use typst::env::{Env, FsLoader}; use typst::env::{Env, FsLoader};
use typst::exec::State; use typst::exec::State;
@ -39,11 +40,12 @@ fn main() -> anyhow::Result<()> {
loader.search_system(); loader.search_system();
let mut env = Env::new(loader); let mut env = Env::new(loader);
let mut cache = Cache::new();
let scope = library::new(); let scope = library::new();
let state = State::default(); let state = State::default();
let Pass { output: frames, diags } = typeset(&mut env, &src, &scope, state); let Pass { output: frames, diags } =
typeset(&mut env, &mut cache, &src, &scope, state);
if !diags.is_empty() { if !diags.is_empty() {
let map = LineMap::new(&src); let map = LineMap::new(&src);
for diag in diags { for diag in diags {

View File

@ -13,6 +13,7 @@ use tiny_skia::{
use ttf_parser::{GlyphId, OutlineBuilder}; use ttf_parser::{GlyphId, OutlineBuilder};
use walkdir::WalkDir; use walkdir::WalkDir;
use typst::cache::Cache;
use typst::color; use typst::color;
use typst::diag::{Diag, DiagSet, Level, Pass}; use typst::diag::{Diag, DiagSet, Level, Pass};
use typst::env::{Env, FsLoader, ImageId}; use typst::env::{Env, FsLoader, ImageId};
@ -212,7 +213,6 @@ fn test_part(
let compare_ref = local_compare_ref.unwrap_or(compare_ref); let compare_ref = local_compare_ref.unwrap_or(compare_ref);
let mut scope = library::new(); let mut scope = library::new();
let panics = Rc::new(RefCell::new(vec![])); let panics = Rc::new(RefCell::new(vec![]));
register_helpers(&mut scope, Rc::clone(&panics)); register_helpers(&mut scope, Rc::clone(&panics));
@ -222,7 +222,8 @@ fn test_part(
state.page.size = Size::new(Length::pt(120.0), Length::raw(f64::INFINITY)); state.page.size = Size::new(Length::pt(120.0), Length::raw(f64::INFINITY));
state.page.margins = Sides::splat(Some(Length::pt(10.0).into())); state.page.margins = Sides::splat(Some(Length::pt(10.0).into()));
let Pass { output: mut frames, diags } = typeset(env, &src, &scope, state); let Pass { output: mut frames, diags } =
typeset(env, &mut Cache::new(), &src, &scope, state);
if !compare_ref { if !compare_ref {
frames.clear(); frames.clear();
} }