Merge pull request #34 from typst/cfg-conditional

Put incremental compilation behind feature
This commit is contained in:
Laurenz 2021-06-30 00:43:53 +02:00 committed by GitHub
commit d5d2b80699
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 259 additions and 90 deletions

View File

@ -36,6 +36,9 @@ jobs:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
override: true override: true
- name: Dependency cache
uses: Swatinem/rust-cache@v1
- name: Build - name: Build
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
@ -47,3 +50,9 @@ jobs:
with: with:
command: test command: test
args: --manifest-path typst/Cargo.toml --all-features args: --manifest-path typst/Cargo.toml --all-features
- name: Test without incremental
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path typst/Cargo.toml --no-default-features --features fs

View File

@ -5,9 +5,10 @@ authors = ["The Typst Project Developers"]
edition = "2018" edition = "2018"
[features] [features]
default = ["cli", "fs"] default = ["cli", "fs", "layout-cache"]
cli = ["anyhow", "fs", "same-file"] cli = ["anyhow", "fs", "same-file"]
fs = ["dirs", "memmap2", "same-file", "walkdir"] fs = ["dirs", "memmap2", "same-file", "walkdir"]
layout-cache = []
[workspace] [workspace]
members = ["bench"] members = ["bench"]

View File

@ -5,9 +5,13 @@ authors = ["The Typst Project Developers"]
edition = "2018" edition = "2018"
publish = false publish = false
[features]
default = ["layout-cache"]
layout-cache = ["typst/layout-cache"]
[dev-dependencies] [dev-dependencies]
criterion = "0.3" criterion = { version = "0.3", features = ["html_reports"] }
typst = { path = ".." } typst = { path = "..", default-features = false, features = ["fs"] }
[[bench]] [[bench]]
name = "typst" name = "typst"

View File

@ -1,14 +1,17 @@
use std::cell::RefCell;
use std::path::Path; use std::path::Path;
use std::rc::Rc; use std::rc::Rc;
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{criterion_group, criterion_main, Criterion};
use typst::eval::eval; use typst::cache::Cache;
use typst::exec::exec; use typst::eval::{eval, Module, Scope};
use typst::exec::{exec, State};
use typst::export::pdf; use typst::export::pdf;
use typst::layout::layout; use typst::layout::{self, layout, Frame};
use typst::loading::FsLoader; use typst::loading::FsLoader;
use typst::parse::parse; use typst::parse::parse;
use typst::syntax;
use typst::typeset; use typst::typeset;
const FONT_DIR: &str = "../fonts"; const FONT_DIR: &str = "../fonts";
@ -16,42 +19,144 @@ const TYP_DIR: &str = "../tests/typ";
const CASES: &[&str] = &["coma.typ", "text/basic.typ"]; const CASES: &[&str] = &["coma.typ", "text/basic.typ"];
fn benchmarks(c: &mut Criterion) { fn benchmarks(c: &mut Criterion) {
let mut loader = FsLoader::new(); let ctx = Context::new();
loader.search_path(FONT_DIR);
let mut cache = typst::cache::Cache::new(&loader);
let scope = typst::library::new();
let state = typst::exec::State::default();
for case in CASES { for case in CASES {
let path = Path::new(TYP_DIR).join(case); let path = Path::new(TYP_DIR).join(case);
let name = path.file_stem().unwrap().to_string_lossy(); let name = path.file_stem().unwrap().to_string_lossy();
let src = std::fs::read_to_string(&path).unwrap();
let case = Case::new(src, ctx.clone());
/// Bench with all caches.
macro_rules! bench { macro_rules! bench {
($step:literal: $code:expr) => { ($step:literal, setup = |$cache:ident| $setup:expr, code = $code:expr $(,)?) => {
c.bench_function(&format!("{}-{}", $step, name), |b| { c.bench_function(&format!("{}-{}", $step, name), |b| {
b.iter(|| { b.iter_batched(
cache.layout.clear(); || {
$code let mut borrowed = ctx.borrow_mut();
}); let $cache = &mut borrowed.cache;
$setup
},
|_| $code,
criterion::BatchSize::PerIteration,
)
}); });
}; };
($step:literal, $code:expr) => {
c.bench_function(&format!("{}-{}", $step, name), |b| b.iter(|| $code));
};
} }
// Prepare intermediate results, run warm and fill caches. bench!("parse", case.parse());
let src = std::fs::read_to_string(&path).unwrap(); bench!("eval", case.eval());
let tree = Rc::new(parse(&src).output); bench!("exec", case.exec());
let evaluated = eval(&mut loader, &mut cache, Some(&path), tree.clone(), &scope);
let executed = exec(&evaluated.output.template, state.clone());
let layouted = layout(&mut loader, &mut cache, &executed.output);
// Bench! #[cfg(not(feature = "layout-cache"))]
bench!("parse": parse(&src)); {
bench!("eval": eval(&mut loader, &mut cache, Some(&path), tree.clone(), &scope)); bench!("layout", case.layout());
bench!("exec": exec(&evaluated.output.template, state.clone())); bench!("typeset", case.typeset());
bench!("layout": layout(&mut loader, &mut cache, &executed.output)); }
bench!("typeset": typeset(&mut loader, &mut cache, Some(&path), &src, &scope, state.clone()));
bench!("pdf": pdf(&cache, &layouted)); #[cfg(feature = "layout-cache")]
{
bench!(
"layout",
setup = |cache| cache.layout.clear(),
code = case.layout(),
);
bench!(
"typeset",
setup = |cache| cache.layout.clear(),
code = case.typeset(),
);
bench!("layout-cached", case.layout());
bench!("typeset-cached", case.typeset());
}
bench!("pdf", case.pdf());
}
}
/// The context required for benchmarking a case.
struct Context {
loader: FsLoader,
cache: Cache,
}
impl Context {
fn new() -> Rc<RefCell<Self>> {
let mut loader = FsLoader::new();
loader.search_path(FONT_DIR);
let cache = Cache::new(&loader);
Rc::new(RefCell::new(Self { loader, cache }))
}
}
/// A test case with prepared intermediate results.
struct Case {
ctx: Rc<RefCell<Context>>,
src: String,
scope: Scope,
state: State,
ast: Rc<syntax::Tree>,
module: Module,
tree: layout::Tree,
frames: Vec<Rc<Frame>>,
}
impl Case {
fn new(src: impl Into<String>, ctx: Rc<RefCell<Context>>) -> Self {
let mut borrowed = ctx.borrow_mut();
let Context { loader, cache } = &mut *borrowed;
let scope = typst::library::new();
let state = typst::exec::State::default();
let src = src.into();
let ast = Rc::new(parse(&src).output);
let module = eval(loader, cache, None, ast.clone(), &scope).output;
let tree = exec(&module.template, state.clone()).output;
let frames = layout(loader, cache, &tree);
drop(borrowed);
Self {
ctx,
src,
scope,
state,
ast,
module,
tree,
frames,
}
}
fn parse(&self) -> syntax::Tree {
parse(&self.src).output
}
fn eval(&self) -> Module {
let mut borrowed = self.ctx.borrow_mut();
let Context { loader, cache } = &mut *borrowed;
eval(loader, cache, None, self.ast.clone(), &self.scope).output
}
fn exec(&self) -> layout::Tree {
exec(&self.module.template, self.state.clone()).output
}
fn layout(&self) -> Vec<Rc<Frame>> {
let mut borrowed = self.ctx.borrow_mut();
let Context { loader, cache } = &mut *borrowed;
layout(loader, cache, &self.tree)
}
fn typeset(&self) -> Vec<Rc<Frame>> {
let mut borrowed = self.ctx.borrow_mut();
let Context { loader, cache } = &mut *borrowed;
let state = self.state.clone();
typeset(loader, cache, None, &self.src, &self.scope, state).output
}
fn pdf(&self) -> Vec<u8> {
let ctx = self.ctx.borrow();
pdf(&ctx.cache, &self.frames)
} }
} }

View File

@ -2,6 +2,7 @@
use crate::font::FontCache; use crate::font::FontCache;
use crate::image::ImageCache; use crate::image::ImageCache;
#[cfg(feature = "layout-cache")]
use crate::layout::LayoutCache; use crate::layout::LayoutCache;
use crate::loading::Loader; use crate::loading::Loader;
@ -12,6 +13,7 @@ pub struct Cache {
/// Caches decoded images. /// Caches decoded images.
pub image: ImageCache, pub image: ImageCache,
/// Caches layouting artifacts. /// Caches layouting artifacts.
#[cfg(feature = "layout-cache")]
pub layout: LayoutCache, pub layout: LayoutCache,
} }
@ -21,6 +23,7 @@ impl Cache {
Self { Self {
font: FontCache::new(loader), font: FontCache::new(loader),
image: ImageCache::new(), image: ImageCache::new(),
#[cfg(feature = "layout-cache")]
layout: LayoutCache::new(), layout: LayoutCache::new(),
} }
} }

View File

@ -1,7 +1,8 @@
use super::*; use super::*;
/// A node that places a rectangular filled background behind its child. /// A node that places a rectangular filled background behind its child.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub struct BackgroundNode { pub struct BackgroundNode {
/// The kind of shape to use as a background. /// The kind of shape to use as a background.
pub shape: BackgroundShape, pub shape: BackgroundShape,

View File

@ -1,7 +1,8 @@
use super::*; use super::*;
/// A node that can fix its child's width and height. /// A node that can fix its child's width and height.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub struct FixedNode { pub struct FixedNode {
/// The fixed width, if any. /// The fixed width, if any.
pub width: Option<Linear>, pub width: Option<Linear>,

View File

@ -1,7 +1,8 @@
use super::*; use super::*;
/// A node that arranges its children in a grid. /// A node that arranges its children in a grid.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub struct GridNode { pub struct GridNode {
/// The `main` and `cross` directions of this grid. /// The `main` and `cross` directions of this grid.
/// ///

View File

@ -4,7 +4,8 @@ use crate::image::ImageId;
use ::image::GenericImageView; use ::image::GenericImageView;
/// An image node. /// An image node.
#[derive(Debug, Copy, Clone, PartialEq, Hash)] #[derive(Debug, Copy, Clone, PartialEq)]
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub struct ImageNode { pub struct ImageNode {
/// The id of the image file. /// The id of the image file.
pub id: ImageId, pub id: ImageId,

View File

@ -1,3 +1,4 @@
#[cfg(feature = "layout-cache")]
use std::collections::{hash_map::Entry, HashMap}; use std::collections::{hash_map::Entry, HashMap};
use std::ops::Deref; use std::ops::Deref;
@ -5,6 +6,7 @@ use super::*;
/// Caches layouting artifacts. /// Caches layouting artifacts.
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
#[cfg(feature = "layout-cache")]
pub struct LayoutCache { pub struct LayoutCache {
/// Maps from node hashes to the resulting frames and regions in which the /// Maps from node hashes to the resulting frames and regions in which the
/// frames are valid. The right hand side of the hash map is a vector of /// frames are valid. The right hand side of the hash map is a vector of
@ -15,6 +17,7 @@ pub struct LayoutCache {
age: usize, age: usize,
} }
#[cfg(feature = "layout-cache")]
impl LayoutCache { impl LayoutCache {
/// Create a new, empty layout cache. /// Create a new, empty layout cache.
pub fn new() -> Self { pub fn new() -> Self {
@ -100,6 +103,7 @@ impl LayoutCache {
/// Cached frames from past layouting. /// Cached frames from past layouting.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[cfg(feature = "layout-cache")]
pub struct FramesEntry { pub struct FramesEntry {
/// The cached frames for a node. /// The cached frames for a node.
pub frames: Vec<Constrained<Rc<Frame>>>, pub frames: Vec<Constrained<Rc<Frame>>>,
@ -112,6 +116,7 @@ pub struct FramesEntry {
temperature: [usize; 5], temperature: [usize; 5],
} }
#[cfg(feature = "layout-cache")]
impl FramesEntry { impl FramesEntry {
/// Construct a new instance. /// Construct a new instance.
pub fn new(frames: Vec<Constrained<Rc<Frame>>>, level: usize) -> Self { pub fn new(frames: Vec<Constrained<Rc<Frame>>>, level: usize) -> Self {
@ -205,6 +210,7 @@ impl Constraints {
} }
} }
#[cfg(feature = "layout-cache")]
fn check(&self, regions: &Regions) -> bool { fn check(&self, regions: &Regions) -> bool {
if self.expand != regions.expand { if self.expand != regions.expand {
return false; return false;

View File

@ -24,9 +24,12 @@ pub use stack::*;
use std::any::Any; use std::any::Any;
use std::fmt::{self, Debug, Formatter}; use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::Hash;
#[cfg(feature = "layout-cache")]
use std::hash::Hasher;
use std::rc::Rc; use std::rc::Rc;
#[cfg(feature = "layout-cache")]
use fxhash::FxHasher64; use fxhash::FxHasher64;
use crate::cache::Cache; use crate::cache::Cache;
@ -35,7 +38,12 @@ use crate::loading::Loader;
/// Layout a tree into a collection of frames. /// Layout a tree into a collection of frames.
pub fn layout(loader: &mut dyn Loader, cache: &mut Cache, tree: &Tree) -> Vec<Rc<Frame>> { pub fn layout(loader: &mut dyn Loader, cache: &mut Cache, tree: &Tree) -> Vec<Rc<Frame>> {
tree.layout(&mut LayoutContext { loader, cache, level: 0 }) tree.layout(&mut LayoutContext {
loader,
cache,
#[cfg(feature = "layout-cache")]
level: 0,
})
} }
/// A tree of layout nodes. /// A tree of layout nodes.
@ -77,22 +85,35 @@ impl PageRun {
/// A wrapper around a dynamic layouting node. /// A wrapper around a dynamic layouting node.
pub struct AnyNode { pub struct AnyNode {
node: Box<dyn Bounds>, node: Box<dyn Bounds>,
#[cfg(feature = "layout-cache")]
hash: u64, hash: u64,
} }
impl AnyNode { impl AnyNode {
/// Create a new instance from any node that satisifies the required bounds. /// Create a new instance from any node that satisifies the required bounds.
#[cfg(feature = "layout-cache")]
pub fn new<T>(node: T) -> Self pub fn new<T>(node: T) -> Self
where where
T: Layout + Debug + Clone + PartialEq + Hash + 'static, T: Layout + Debug + Clone + PartialEq + Hash + 'static,
{ {
let hash = {
let mut state = FxHasher64::default(); let mut state = FxHasher64::default();
node.type_id().hash(&mut state); node.type_id().hash(&mut state);
node.hash(&mut state); node.hash(&mut state);
let hash = state.finish(); state.finish()
};
Self { node: Box::new(node), hash } Self { node: Box::new(node), hash }
} }
/// Create a new instance from any node that satisifies the required bounds.
#[cfg(not(feature = "layout-cache"))]
pub fn new<T>(node: T) -> Self
where
T: Layout + Debug + Clone + PartialEq + 'static,
{
Self { node: Box::new(node) }
}
} }
impl Layout for AnyNode { impl Layout for AnyNode {
@ -101,6 +122,8 @@ impl Layout for AnyNode {
ctx: &mut LayoutContext, ctx: &mut LayoutContext,
regions: &Regions, regions: &Regions,
) -> Vec<Constrained<Rc<Frame>>> { ) -> Vec<Constrained<Rc<Frame>>> {
#[cfg(feature = "layout-cache")]
{
ctx.level += 1; ctx.level += 1;
let frames = let frames =
ctx.cache.layout.get(self.hash, regions.clone()).unwrap_or_else(|| { ctx.cache.layout.get(self.hash, regions.clone()).unwrap_or_else(|| {
@ -111,12 +134,16 @@ impl Layout for AnyNode {
ctx.level -= 1; ctx.level -= 1;
frames frames
} }
#[cfg(not(feature = "layout-cache"))]
self.node.layout(ctx, regions)
}
} }
impl Clone for AnyNode { impl Clone for AnyNode {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
node: self.node.dyn_clone(), node: self.node.dyn_clone(),
#[cfg(feature = "layout-cache")]
hash: self.hash, hash: self.hash,
} }
} }
@ -128,6 +155,7 @@ impl PartialEq for AnyNode {
} }
} }
#[cfg(feature = "layout-cache")]
impl Hash for AnyNode { impl Hash for AnyNode {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.hash); state.write_u64(self.hash);
@ -184,6 +212,7 @@ pub struct LayoutContext<'a> {
/// A cache for loaded fonts and artifacts from past layouting. /// A cache for loaded fonts and artifacts from past layouting.
pub cache: &'a mut Cache, pub cache: &'a mut Cache,
/// How deeply nested the current layout tree position is. /// How deeply nested the current layout tree position is.
#[cfg(feature = "layout-cache")]
pub level: usize, pub level: usize,
} }

View File

@ -1,7 +1,8 @@
use super::*; use super::*;
/// A node that adds padding to its child. /// A node that adds padding to its child.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub struct PadNode { pub struct PadNode {
/// The amount of padding. /// The amount of padding.
pub padding: Sides<Linear>, pub padding: Sides<Linear>,

View File

@ -11,7 +11,8 @@ use crate::util::{RangeExt, SliceExt};
type Range = std::ops::Range<usize>; type Range = std::ops::Range<usize>;
/// A node that arranges its children into a paragraph. /// A node that arranges its children into a paragraph.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub struct ParNode { pub struct ParNode {
/// The inline direction of this paragraph. /// The inline direction of this paragraph.
pub dir: Dir, pub dir: Dir,
@ -22,7 +23,8 @@ pub struct ParNode {
} }
/// A child of a paragraph node. /// A child of a paragraph node.
#[derive(Clone, PartialEq, Hash)] #[derive(Clone, PartialEq)]
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub enum ParChild { pub enum ParChild {
/// Spacing between other nodes. /// Spacing between other nodes.
Spacing(Length), Spacing(Length),

View File

@ -3,7 +3,8 @@ use decorum::N64;
use super::*; use super::*;
/// A node that stacks its children. /// A node that stacks its children.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub struct StackNode { pub struct StackNode {
/// The `main` and `cross` directions of this stack. /// The `main` and `cross` directions of this stack.
/// ///
@ -19,7 +20,8 @@ pub struct StackNode {
} }
/// A child of a stack node. /// A child of a stack node.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "layout-cache", derive(Hash))]
pub enum StackChild { pub enum StackChild {
/// Spacing between other nodes. /// Spacing between other nodes.
Spacing(Length), Spacing(Length),

View File

@ -272,6 +272,8 @@ fn test_part(
} }
} }
#[cfg(feature = "layout-cache")]
{
let reference_cache = cache.layout.clone(); let reference_cache = cache.layout.clone();
for level in 0 .. reference_cache.levels() { for level in 0 .. reference_cache.levels() {
cache.layout = reference_cache.clone(); cache.layout = reference_cache.clone();
@ -311,6 +313,7 @@ fn test_part(
cache.layout = reference_cache; cache.layout = reference_cache;
cache.layout.turnaround(); cache.layout.turnaround();
}
if !compare_ref { if !compare_ref {
layouted.clear(); layouted.clear();