Put incremental compilation behind feature

This commit is contained in:
Martin Haug 2021-06-29 12:33:24 +02:00
parent 8ea05739af
commit 21d919e2d2
12 changed files with 111 additions and 60 deletions

View File

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

View File

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

View File

@ -1,7 +1,8 @@
use super::*;
/// 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 {
/// The kind of shape to use as a background.
pub shape: BackgroundShape,

View File

@ -1,7 +1,8 @@
use super::*;
/// 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 {
/// The fixed width, if any.
pub width: Option<Linear>,

View File

@ -1,7 +1,8 @@
use super::*;
/// 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 {
/// The `main` and `cross` directions of this grid.
///

View File

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

View File

@ -1,3 +1,4 @@
#[cfg(feature = "layout-cache")]
use std::collections::{hash_map::Entry, HashMap};
use std::ops::Deref;
@ -5,6 +6,7 @@ use super::*;
/// Caches layouting artifacts.
#[derive(Default, Debug, Clone)]
#[cfg(feature = "layout-cache")]
pub struct LayoutCache {
/// 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
@ -15,6 +17,7 @@ pub struct LayoutCache {
age: usize,
}
#[cfg(feature = "layout-cache")]
impl LayoutCache {
/// Create a new, empty layout cache.
pub fn new() -> Self {
@ -100,6 +103,7 @@ impl LayoutCache {
/// Cached frames from past layouting.
#[derive(Debug, Clone)]
#[cfg(feature = "layout-cache")]
pub struct FramesEntry {
/// The cached frames for a node.
pub frames: Vec<Constrained<Rc<Frame>>>,
@ -112,6 +116,7 @@ pub struct FramesEntry {
temperature: [usize; 5],
}
#[cfg(feature = "layout-cache")]
impl FramesEntry {
/// Construct a new instance.
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 {
if self.expand != regions.expand {
return false;

View File

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

View File

@ -1,7 +1,8 @@
use super::*;
/// 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 {
/// The amount of padding.
pub padding: Sides<Linear>,

View File

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

View File

@ -3,7 +3,8 @@ use decorum::N64;
use super::*;
/// 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 {
/// The `main` and `cross` directions of this stack.
///
@ -19,7 +20,8 @@ pub struct StackNode {
}
/// 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 {
/// Spacing between other nodes.
Spacing(Length),

View File

@ -272,46 +272,49 @@ fn test_part(
}
}
let reference_cache = cache.layout.clone();
for level in 0 .. reference_cache.levels() {
cache.layout = reference_cache.clone();
cache.layout.retain(|x| x == level);
if cache.layout.frames.is_empty() {
continue;
#[cfg(feature = "layout-cache")]
{
let reference_cache = cache.layout.clone();
for level in 0 .. reference_cache.levels() {
cache.layout = reference_cache.clone();
cache.layout.retain(|x| x == level);
if cache.layout.frames.is_empty() {
continue;
}
cache.layout.turnaround();
let cached_result = layout(loader, cache, &executed.output);
let misses = cache
.layout
.frames
.iter()
.flat_map(|(_, e)| e)
.filter(|e| e.level == level && !e.hit() && e.age() == 2)
.count();
if misses > 0 {
ok = false;
println!(
" Recompilation had {} cache misses on level {} (Subtest {}) ❌",
misses, level, i
);
}
if cached_result != layouted {
ok = false;
println!(
" Recompilation of subtest {} differs from clean pass ❌",
i
);
}
}
cache.layout = reference_cache;
cache.layout.turnaround();
let cached_result = layout(loader, cache, &executed.output);
let misses = cache
.layout
.frames
.iter()
.flat_map(|(_, e)| e)
.filter(|e| e.level == level && !e.hit() && e.age() == 2)
.count();
if misses > 0 {
ok = false;
println!(
" Recompilation had {} cache misses on level {} (Subtest {}) ❌",
misses, level, i
);
}
if cached_result != layouted {
ok = false;
println!(
" Recompilation of subtest {} differs from clean pass ❌",
i
);
}
}
cache.layout = reference_cache;
cache.layout.turnaround();
if !compare_ref {
layouted.clear();
}