[WIP] Run iterations in parallel

This commit is contained in:
Laurenz 2024-06-10 14:34:16 +02:00
parent 7fa86eed0e
commit 59b183fbcb
4 changed files with 107 additions and 44 deletions

View File

@ -128,13 +128,13 @@ pub struct Sink {
/// because the introspector is not yet ready. We first ignore that and /// because the introspector is not yet ready. We first ignore that and
/// proceed with empty content and only if the error remains by the end /// proceed with empty content and only if the error remains by the end
/// of the last iteration, we promote it. /// of the last iteration, we promote it.
delayed: EcoVec<SourceDiagnostic>, pub(crate) delayed: EcoVec<SourceDiagnostic>,
/// Warnings emitted during iteration. /// Warnings emitted during iteration.
warnings: EcoVec<SourceDiagnostic>, pub(crate) warnings: EcoVec<SourceDiagnostic>,
/// Hashes of all warning's spans and messages for warning deduplication. /// Hashes of all warning's spans and messages for warning deduplication.
warnings_set: HashSet<u128>, warnings_set: HashSet<u128>,
/// A sequence of traced values for a span. /// A sequence of traced values for a span.
values: EcoVec<(Value, Option<Styles>)>, pub(crate) values: EcoVec<(Value, Option<Styles>)>,
} }
impl Sink { impl Sink {
@ -186,7 +186,7 @@ impl Sink {
} }
/// Extend from another sink. /// Extend from another sink.
fn extend( pub fn extend(
&mut self, &mut self,
delayed: EcoVec<SourceDiagnostic>, delayed: EcoVec<SourceDiagnostic>,
warnings: EcoVec<SourceDiagnostic>, warnings: EcoVec<SourceDiagnostic>,

View File

@ -38,21 +38,25 @@ pub struct Introspector {
} }
impl Introspector { impl Introspector {
/// Applies new frames in-place, reusing the existing allocations. /// Build a new introspector for a page collection.
#[typst_macros::time(name = "introspect")] #[typst_macros::time(name = "introspect")]
pub fn rebuild(&mut self, pages: &[Page]) { pub fn new(pages: &[Page]) -> Self {
self.pages = pages.len(); let mut this = Self {
self.elems.clear(); pages: pages.len(),
self.labels.clear(); elems: IndexMap::new(),
self.keys.clear(); labels: HashMap::new(),
self.page_numberings.clear(); keys: HashMap::new(),
self.queries.clear(); page_numberings: Vec::with_capacity(pages.len()),
queries: QueryCache::default(),
};
for (i, page) in pages.iter().enumerate() { for (i, page) in pages.iter().enumerate() {
let page_nr = NonZeroUsize::new(1 + i).unwrap(); let page_nr = NonZeroUsize::new(1 + i).unwrap();
self.extract(&page.frame, page_nr, Transform::identity()); this.extract(&page.frame, page_nr, Transform::identity());
self.page_numberings.push(page.numbering.clone()); this.page_numberings.push(page.numbering.clone());
} }
this
} }
/// Extract metadata from a frame. /// Extract metadata from a frame.
@ -328,10 +332,6 @@ impl QueryCache {
fn insert(&self, hash: u128, output: EcoVec<Content>) { fn insert(&self, hash: u128, output: EcoVec<Content>) {
self.0.write().unwrap().insert(hash, output); self.0.write().unwrap().insert(hash, output);
} }
fn clear(&mut self) {
self.0.get_mut().unwrap().clear();
}
} }
impl Clone for QueryCache { impl Clone for QueryCache {

View File

@ -58,15 +58,17 @@ pub use typst_utils as utils;
use std::collections::HashSet; use std::collections::HashSet;
use std::ops::{Deref, Range}; use std::ops::{Deref, Range};
use std::sync::{Arc, Mutex};
use comemo::{Track, Tracked, Validate}; use comemo::{Track, Tracked, Validate};
use ecow::{EcoString, EcoVec}; use ecow::{EcoString, EcoVec};
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
use typst_timing::{timed, TimingScope}; use typst_timing::{timed, TimingScope};
use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult, Warned}; use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult, Warned};
use crate::engine::{Engine, Route, Sink, Traced}; use crate::engine::{Engine, Route, Sink, Traced};
use crate::foundations::{ use crate::foundations::{
Array, Bytes, Datetime, Dict, Module, Scope, StyleChain, Styles, Value, Array, Bytes, Content, Datetime, Dict, Module, Scope, StyleChain, Styles, Value,
}; };
use crate::introspection::Introspector; use crate::introspection::Introspector;
use crate::layout::{Alignment, Dir}; use crate::layout::{Alignment, Dir};
@ -105,8 +107,8 @@ fn compile_inner(
traced: Tracked<Traced>, traced: Tracked<Traced>,
sink: &mut Sink, sink: &mut Sink,
) -> SourceResult<Document> { ) -> SourceResult<Document> {
let library = world.library(); // Yes, this is a hack. But it's ok, this is just an experimental branch.
let styles = StyleChain::new(&library.styles); static INTROSPECTORS: Mutex<Vec<Arc<Introspector>>> = Mutex::new(Vec::new());
// First evaluate the main source file into a module. // First evaluate the main source file into a module.
let content = crate::eval::eval( let content = crate::eval::eval(
@ -118,36 +120,57 @@ fn compile_inner(
)? )?
.content(); .content();
// If we have introspectors from previous compilations, we can speed up
// compilation with a small speculative trick: Since quite often the
// iterations we go through will be exactly the same ones as previously with
// no relevant introspector changes, we can run all iterations in parallel
// using the previous introspectors.
let mut speculation = INTROSPECTORS
.lock()
.unwrap()
.clone()
.par_iter()
.enumerate()
.map(|(iter, introspector)| {
let mut sink = Sink::new();
(layout(world, &introspector, traced, &mut sink, &content, iter), sink)
})
.collect::<Vec<_>>()
.into_iter();
let mut iter = 0; let mut iter = 0;
let mut document = Document::default(); let mut introspector = Arc::new(Introspector::new(&[]));
let mut introspectors = vec![];
// Relayout until all introspections stabilize. // Relayout until all introspections stabilize.
// If that doesn't happen within five attempts, we give up. // If that doesn't happen within five attempts, we give up.
loop { let document = loop {
// The name of the iterations for timing scopes. // Save this iteration's introspector.
const ITER_NAMES: &[&str] = introspectors.push(introspector.clone());
&["layout (1)", "layout (2)", "layout (3)", "layout (4)", "layout (5)"];
let _scope = TimingScope::new(ITER_NAMES[iter], None);
// Clear delayed errors. // Clear delayed errors.
sink.delayed(); sink.delayed();
let constraint = <Introspector as Validate>::Constraint::new(); // Try to use a speculative result if it was valid for the
let mut engine = Engine { // real current introspector. If not, layout as usual.
world, let (result, constraint) = if let Some((pair, subsink)) =
introspector: document.introspector.track_with(&constraint), speculation.next().filter(|((_, constraint), _)| {
traced, timed!("check speculation", introspector.validate(&constraint))
sink: sink.track_mut(), }) {
route: Route::default(), sink.extend(subsink.delayed, subsink.warnings, subsink.values);
pair
} else {
layout(world, &introspector, traced, sink, &content, iter)
}; };
// Layout! let document = result?;
document = content.layout_document(&mut engine, styles)?; introspector = document.introspector.clone();
document.introspector.rebuild(&document.pages);
// Bump the iteration.
iter += 1; iter += 1;
if timed!("check stabilized", document.introspector.validate(&constraint)) { if timed!("check stabilized", introspector.validate(&constraint)) {
break; break document;
} }
if iter >= 5 { if iter >= 5 {
@ -155,9 +178,12 @@ fn compile_inner(
Span::detached(), "layout did not converge within 5 attempts"; Span::detached(), "layout did not converge within 5 attempts";
hint: "check if any states or queries are updating themselves" hint: "check if any states or queries are updating themselves"
)); ));
break; break document;
}
} }
};
// Save the introspectors for next time.
*INTROSPECTORS.lock().unwrap() = introspectors;
// Promote delayed errors. // Promote delayed errors.
let delayed = sink.delayed(); let delayed = sink.delayed();
@ -168,6 +194,39 @@ fn compile_inner(
Ok(document) Ok(document)
} }
/// Run a single layout iteration with the given introspector.
///
/// Returns the document and the introspector constraints for it.
fn layout(
world: Tracked<dyn World + '_>,
introspector: &Introspector,
traced: Tracked<Traced>,
sink: &mut Sink,
content: &Content,
iter: usize,
) -> (SourceResult<Document>, <Introspector as Validate>::Constraint) {
// The name of the iterations for timing scopes.
const ITERS: &[&str] =
&["layout (1)", "layout (2)", "layout (3)", "layout (4)", "layout (5)"];
let _scope = TimingScope::new(ITERS[iter], None);
let constraint = <Introspector as Validate>::Constraint::new();
let mut engine = Engine {
world,
introspector: introspector.track_with(&constraint),
traced,
sink: sink.track_mut(),
route: Route::default(),
};
let library = world.library();
let styles = StyleChain::new(&library.styles);
// Layout!
let document = content.layout_document(&mut engine, styles);
(document, constraint)
}
/// Deduplicate diagnostics. /// Deduplicate diagnostics.
fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic> { fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic> {
let mut unique = HashSet::new(); let mut unique = HashSet::new();
@ -189,7 +248,7 @@ fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic>
/// information on when something can change. For example, fonts typically don't /// information on when something can change. For example, fonts typically don't
/// change and can thus even be cached across multiple compilations (for /// change and can thus even be cached across multiple compilations (for
/// long-running applications like `typst watch`). Source files on the other /// long-running applications like `typst watch`). Source files on the other
/// hand can change and should thus be cleared after each compilation. Advanced /// hand can change and might thus be cleared after each compilation. Advanced
/// clients like language servers can also retain the source files and /// clients like language servers can also retain the source files and
/// [edit](Source::edit) them in-place to benefit from better incremental /// [edit](Source::edit) them in-place to benefit from better incremental
/// performance. /// performance.

View File

@ -1,3 +1,5 @@
use std::sync::Arc;
use ecow::EcoString; use ecow::EcoString;
use crate::diag::{bail, HintedStrResult, SourceResult}; use crate::diag::{bail, HintedStrResult, SourceResult};
@ -107,13 +109,15 @@ impl Packed<DocumentElem> {
pages.extend(result?.finalize(engine, &mut page_counter)?); pages.extend(result?.finalize(engine, &mut page_counter)?);
} }
let introspector = Introspector::new(&pages);
Ok(Document { Ok(Document {
pages, pages,
title: DocumentElem::title_in(styles).map(|content| content.plain_text()), title: DocumentElem::title_in(styles).map(|content| content.plain_text()),
author: DocumentElem::author_in(styles).0, author: DocumentElem::author_in(styles).0,
keywords: DocumentElem::keywords_in(styles).0, keywords: DocumentElem::keywords_in(styles).0,
date: DocumentElem::date_in(styles), date: DocumentElem::date_in(styles),
introspector: Introspector::default(), introspector: Arc::new(introspector),
}) })
} }
} }
@ -154,7 +158,7 @@ pub struct Document {
/// The document's creation date. /// The document's creation date.
pub date: Smart<Option<Datetime>>, pub date: Smart<Option<Datetime>>,
/// Provides the ability to execute queries on the document. /// Provides the ability to execute queries on the document.
pub introspector: Introspector, pub introspector: Arc<Introspector>,
} }
#[cfg(test)] #[cfg(test)]